// You can redistribute this software and/or modify it under the terms of
// the Infozone Software License version 2 published by the Infozone Group
// (http://www.infozone-group.org).
//
// Copyright (C) 2000 by The Infozone Group. All rights reserved.
//
// $Id: XUpdateQueryImpl.java,v 1.6 2000/12/07 21:12:54 andreas Exp $

package org.infozone.lexus;

import org.infozone.lexus.commands.*;
import org.infozone.tools.xml.queries.XUpdateQuery;

import java.io.*;
import java.lang.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.Parser;
import org.xml.sax.helpers.ParserAdapter;

import org.w3c.dom.*;
import org.w3c.dom.traversal.NodeFilter;

import org.jdom.Document;
import org.jdom.input.DOMBuilder;
import org.jdom.output.XMLOutputter;


/* just for main method */
import org.apache.xerces.parsers.DOMParser;

/**
 * <b>Note:</b>
 *       If the DOM implementation you use does not support DOM-Level 2, some 
 *       features will not work properly. XUpdate uses the method
 *       <code>getNamespaceURI</code> of the <code>org.w3c.dom.Node</code> 
 *       interface. This method returns <code>null</code> if the current Node 
 *       has no namespace. Your DOM implementation should implement this method 
 *       and alway return null, then XUpdate should work better. Nevertheless 
 *       you cannot use some features. Inserting elements and attributes just 
 *       works, if you omit the optional attribute called namespace. For more 
 *       detail about XUpdate look at the 
 *       <a href="http://www.xmldb.org/xupdate/xupdate-wd.html">XUpdate Working Draft</a>.
 */
public class XUpdateQueryImpl implements XUpdateQuery {

    /* The XUpdate namespace-uri. */
    public final static String NAMESPACE_URI = "http://www.xmldb.org/xupdate";
    
    /* Another representation of the query. It is faster than many String comparisons.*/
    protected Vector _query[] = null;
    /* Representation of commands and instructions as IDs. */
    protected CommandConstants _commands = null;
    /* */
    protected NodeFilter _filter = null;
    /* */
    protected Node _namespace = null;
    
    
    /**
     * 
     */
    public XUpdateQueryImpl( ) {
        _commands = new CommandConstants( );
    }
    
    
    /**
     *
     */
    public void setQString( String query ) throws SAXException {
        XUpdateQueryParser xuParser = new XUpdateQueryParser( _commands );

        try {
            SAXParser parser = SAXParserFactory.newInstance( ).newSAXParser( );
            ParserAdapter saxParser = new ParserAdapter( parser.getParser( ) );
            saxParser.setContentHandler( xuParser );
            saxParser.parse( new InputSource( new StringReader( query ) ) );
        } catch (Exception e) {
            throw new SAXException( e.getMessage( ) );
        }
        
        _query = xuParser.getCachedQuery( );
        if (_query[0].size()==0) {
            throw new SAXException( "query contains no XUpdateOperation !" );
        }
    }
    
    
    /**
     *
     */
    public void setNamespace( Node node ) {
        _namespace = node;
    }
    
    
    /**
     *
     */
    public void setNodeFilter( NodeFilter filter ) {
        _filter = filter;
    }


    /**
     *
     */
    public void execute( Node contextNode ) throws Exception {
        CommandObject currentCommand = new DefaultCommand( contextNode );
        Enumeration commands   = _query[0].elements( );
        Enumeration attributes = _query[1].elements( );
        Enumeration characters = _query[2].elements( );
        while (commands.hasMoreElements( )) {
            int id = ((Integer)commands.nextElement( )).intValue( );
            if (id>0) {
                switch ( id ) {
                    case _commands.ATTRIBUTES:
                        currentCommand.submitAttributes( (Hashtable)attributes.nextElement( ) );
                        break;
                    case _commands.CHARACTERS:
                        currentCommand.submitCharacters( (String)characters.nextElement( ) );
                        break;
                    default:
                        if (!currentCommand.submitInstruction( id )) {
                            _commands.setContextNode( contextNode );
                            currentCommand = _commands.commandForID( id );
                            if (currentCommand==null) {
                                throw new Exception( "operation can not have any XUpdate-instruction !" );
                            }
                            currentCommand.reset( );
                        }
                }
            } else {
                if (!currentCommand.executeInstruction( )) {
                    contextNode = currentCommand.execute( );
                    currentCommand = new DefaultCommand( contextNode );
                }
                
            }
        }
    }
    
    
    /**
     * Main-method. You can manually test your update operations.
     */
    public static void main( String args[] ) throws Exception {
        if ( args.length==0 ) {
            System.err.println( "usage: java org.infozone.lexus.XUpdateQueryImpl update document" );
            System.err.println( "       update   - filename of the file which contains XUpdate operations" );
            System.err.println( "       document - filename of the file which contains the content to update" );
            System.exit( 0 );
        }

        // parse the update file
        File file = new File( args[0] );
        BufferedReader br = new BufferedReader( new FileReader ( file ) );
        char[] characters = new char[ new Long(file.length()).intValue() ];
        br.read( characters, 0, new Long( file.length() ).intValue() );
        String queryStr = new String( characters );

        // parse the document file
        Node myDocument = null;
        DOMParser parser = new DOMParser();
        parser.setIncludeIgnorableWhitespace(false);
        parser.parse(args[1]);
        myDocument = parser.getDocument();

        // update the document and print time used for the updates
        XUpdateQueryImpl xq = new XUpdateQueryImpl();

        System.err.println("Starting updates...");
        long timeStart = System.currentTimeMillis();

        xq.setQString ( queryStr );
        xq.execute(myDocument);

        DOMBuilder db = new DOMBuilder();
        org.jdom.Document doc = db.build((org.w3c.dom.Document) myDocument);   
        org.jdom.output.XMLOutputter xo  = new org.jdom.output.XMLOutputter("  ");
        xo.output(doc,System.out);

        long timeEnd = System.currentTimeMillis();
        System.err.println("Updates done in "+(timeEnd-timeStart)+" ms ...");
    }
    
}

