/*
 * FilteredSAXEventSink.java
 * Copyright (C) 1999,2000,2001 The Free Software Foundation
 * 
 * This file is part of GNU JAXP, a library.
 *
 * GNU JAXP 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.
 * 
 * GNU JAXP 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.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Linking this library statically or dynamically with other modules is
 * making a combined work based on this library.  Thus, the terms and
 * conditions of the GNU General Public License cover the whole
 * combination.
 *
 * As a special exception, the copyright holders of this library give you
 * permission to link this library with independent modules to produce an
 * executable, regardless of the license terms of these independent
 * modules, and to copy and distribute the resulting executable under
 * terms of your choice, provided that you also meet, for each linked
 * independent module, the terms and conditions of the license of that
 * module.  An independent module is a module which is not derived from
 * or based on this library.  If you modify this library, you may extend
 * this exception to your version of the library, but you are not
 * obliged to do so.  If you do not wish to do so, delete this
 * exception statement from your version. 
 */

package gnu.xml.dom.ls;

import java.util.LinkedList;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.w3c.dom.ls.LSParserFilter;
import org.w3c.dom.traversal.NodeFilter;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * A SAX event sink that calls out to a parser filter in order to decide
 * whether to insert nodes into the tree.
 *
 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
 */
class FilteredSAXEventSink
  extends SAXEventSink
{

  final LSParserFilter filter;
  final int whatToShow;

  /**
   * Stack of elements to insert.
   */
  LinkedList nodes;
  
  /**
   * Corresponding stack of filter decisions about the nodes.
   */
  LinkedList decisions;

  /**
   * True when rejecting child nodes.
   */
  boolean rejecting;

  FilteredSAXEventSink(LSParserFilter filter)
  {
    this.filter = filter;
    whatToShow = filter.getWhatToShow();
  }

  public void startDocument()
    throws SAXException
  {
    if (interrupted)
      {
        return;
      }
    nodes = new LinkedList();
    decisions = new LinkedList();
    
    super.startDocument();
  }

  public void endDocument()
    throws SAXException
  {
    if (interrupted)
      {
        return;
      }
    super.endDocument();

    switch (getDecision(ctx, false))
      {
      case LSParserFilter.FILTER_REJECT:
        ctx = null;
        doc = null;
        break;
      }
    
    nodes = null;
    decisions = null;
  }

  public void startElement(String uri, String localName, String qName,
                           Attributes atts)
    throws SAXException
  {
    if (rejecting || interrupted)
      {
        return;
      }
    Element element = createElement(uri, localName, qName, atts);
    ctx = element;
    
    short decision = getDecision(element, true);
    nodes.addLast(element);
    decisions.addLast(new Short(decision));

    switch (decision)
      {
      case LSParserFilter.FILTER_REJECT:
        rejecting = true;
        break;
      case LSParserFilter.FILTER_INTERRUPT:
        interrupted = true;
        break;
      }
  }

  protected Attr createAttr(Attributes atts, int index)
  {
    Attr attr = super.createAttr(atts, index);
    short decision = getDecision(attr, false);
    switch (decision)
      {
      case LSParserFilter.FILTER_REJECT:
        return null;
      case LSParserFilter.FILTER_INTERRUPT:
        interrupted = true;
        return null;
      }
    return attr;
  }

  public void endElement(String uri, String localName, String qName)
    throws SAXException
  {
    if (rejecting || interrupted)
      {
        return;
      }
    super.endElement(uri, localName, qName);
    
    Element element = (Element) nodes.removeLast();
    Node parent = nodes.isEmpty() ? doc : (Node) nodes.getLast();
    ctx = parent;
    short decision = ((Short) decisions.removeLast()).shortValue();
    switch (decision)
      {
      case LSParserFilter.FILTER_SKIP:
        // Add all children of element to parent
        for (Node child = element.getFirstChild(); child != null;
             child = child.getNextSibling())
          {
            parent.insertBefore(child, element);
          }
        return;
      case LSParserFilter.FILTER_REJECT:
        rejecting = false;
        break;
      }
    decision = getDecision(element, false);
    switch (decision)
      {
      case LSParserFilter.FILTER_ACCEPT:
        parent.appendChild(element);
        break;
      case LSParserFilter.FILTER_INTERRUPT:
        interrupted = true;
        break;
      }
  }

  public void characters(char[] c, int off, int len)
    throws SAXException
  {
    if (rejecting || interrupted)
      {
        return;
      }
    Text text = createText(c, off, len);
    short decision = getDecision(text, false);
    switch (decision)
      {
      case LSParserFilter.FILTER_ACCEPT:
        ctx.appendChild(text);
        break;
      case LSParserFilter.FILTER_INTERRUPT:
        interrupted = true;
        break;
      }
  }

  public void processingInstruction(String target, String data)
    throws SAXException
  {
    if (rejecting || interrupted || inDTD)
      {
        return;
      }
    Node pi = createProcessingInstruction(target, data);
    short decision = getDecision(pi, false);
    switch (decision)
      {
      case LSParserFilter.FILTER_ACCEPT:
        ctx.appendChild(pi);
        break;
      case LSParserFilter.FILTER_INTERRUPT:
        interrupted = true;
        break;
      }
  }

  public void startDTD(String name, String publicId, String systemId)
    throws SAXException
  {
    if (interrupted)
      {
        return;
      }
    Node doctype = createDocumentType(name, publicId, systemId);
    ctx = doctype;
    inDTD = true;
    nodes.addLast(doctype);
    decisions.addLast(new Short(LSParserFilter.FILTER_ACCEPT));
  }

  public void endDTD()
    throws SAXException
  {
    if (interrupted)
      {
        return;
      }
    Node doctype = (Node) nodes.removeLast();
    decisions.removeLast();
    inDTD = false;
    ctx = doc;
    short decision = getDecision(doctype, false);
    switch (decision)
      {
      case LSParserFilter.FILTER_ACCEPT:
        ctx.appendChild(doctype);
        break;
      case LSParserFilter.FILTER_INTERRUPT:
        interrupted = true;
        break;
      }
  }

  public void comment(char[] c, int off, int len)
    throws SAXException
  {
    if (rejecting || interrupted || inDTD)
      {
        return;
      }
    Node comment = createComment(c, off, len);
    short decision = getDecision(comment, false);
    switch (decision)
      {
      case LSParserFilter.FILTER_ACCEPT:
        ctx.appendChild(comment);
        break;
      case LSParserFilter.FILTER_INTERRUPT:
        interrupted = true;
        break;
      }
  }

  // TODO declarations

  short getDecision(Node node, boolean start)
  {
    boolean show = (whatToShow == NodeFilter.SHOW_ALL);
    if (!show)
      {
        switch (node.getNodeType())
          {
          case Node.ATTRIBUTE_NODE:
            show = ((whatToShow & NodeFilter.SHOW_ATTRIBUTE) != 0);
            break;     
          case Node.TEXT_NODE:
            show = ((whatToShow & NodeFilter.SHOW_TEXT) != 0);
            break;     
          case Node.CDATA_SECTION_NODE:
            show = ((whatToShow & NodeFilter.SHOW_CDATA_SECTION) != 0);
            break;     
          case Node.ELEMENT_NODE:
            show = ((whatToShow & NodeFilter.SHOW_ELEMENT) != 0);
            break;     
          case Node.COMMENT_NODE:
            show = ((whatToShow & NodeFilter.SHOW_COMMENT) != 0);
            break;     
          case Node.DOCUMENT_NODE:
            show = ((whatToShow & NodeFilter.SHOW_DOCUMENT) != 0);
            break;     
          case Node.PROCESSING_INSTRUCTION_NODE:
            show = ((whatToShow & NodeFilter.SHOW_PROCESSING_INSTRUCTION) != 0);
            break;     
          case Node.DOCUMENT_FRAGMENT_NODE:
            show = ((whatToShow & NodeFilter.SHOW_DOCUMENT_FRAGMENT) != 0);
            break;     
          case Node.DOCUMENT_TYPE_NODE:
            show = ((whatToShow & NodeFilter.SHOW_DOCUMENT_TYPE) != 0);
            break;     
          case Node.ENTITY_REFERENCE_NODE:
            show = ((whatToShow & NodeFilter.SHOW_ENTITY_REFERENCE) != 0);
            break;     
          case Node.ENTITY_NODE:
            show = ((whatToShow & NodeFilter.SHOW_ENTITY) != 0);
            break;     
          case Node.NOTATION_NODE:
            show = ((whatToShow & NodeFilter.SHOW_NOTATION) != 0);
            break;     
          }
      }
    if (!show)
      {
        return LSParserFilter.FILTER_ACCEPT;
      }
    if (start)
      {
        return filter.startElement((Element) node);
      }
    return filter.acceptNode(node);
  }

}

