/*
 *  Copyright 2002 Phillip Lord, Victoria University of Manchester
 *
 *  This file is part of myGrid.  Further information, and the
 *  latest version, can be found at http://www.mygrid.info
 *
 *  myGrid is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.
 *
 *  myGrid 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with myGrid; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package uk.ac.man.cs.img.xml.merge;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * MergeDocuments.java
 *
 * Merge two xml documents based on the UniquePredicates. Each tag in
 * the document to be merged in is tested again the
 * UniquePredicates. If a tag in the main document is found to also
 * have a Element judged to be unique by these predicates, it is
 * replaced with the Element from the merge document. Otherwise it is
 * copied recursively into the output document.
 *
 * Created: Wed Nov 27 21:28:43 2002
 *
 * @author Phillip Lord
 * @version $Id: MergeDocuments.java,v 1.5 2003/09/08 11:02:32 lordp Exp $
 */

public class MergeDocuments
{
    private UniquePredicate[] predicates;

    public MergeDocuments( UniquePredicate[] predicates )
    {
        this.predicates = predicates;
    }

    public Document merge( Document mainDoc,
                           Document mergeDoc,
                           Document outputDoc )
    {
        // get a map of uniques from the merge document.
        Map mergeMap = generateUniques( mergeDoc );

        // recurse down the main doc
        recurseTreeForMerge( mainDoc.getDocumentElement(),
                             outputDoc,
                             outputDoc,
                             mergeMap );

        // now we have done the merge, but we want to put stuff and
        // elements from the merge document, which haven't been merged
        // in yet.
        Iterator iter = mergeMap.values().iterator();
        while( iter.hasNext() ){
            Element node = (Element)iter.next();
            outputDoc.getDocumentElement().
                appendChild
                ( outputDoc
                  .importNode
                  ( node, true ) );
        }

        return outputDoc;
    }
    

    /*
     * Recurse down the tree. Add to output if its not unique,
     * or add the element from the merge document if there is
     * a similar unique in it.
     */
    private void recurseTreeForMerge( Node current,
                                      Node currentOutput,
                                      Document outputDocument,
                                      Map mergeMap )
    {
        Node newOutputNode;
        UniquePredicate predicate;
        Node mergeNode = current;
        
        // first we need to see whether the current element is
        // uniquable or not. If it is then we need a unique predicate
        // of this. 
        if( current instanceof Element &&
            ((predicate = getUniquePredicate
              ( (Element)current )) != null) ){
            Element element = (Element)current;
            
            // now that we have a unique predicate, we need to see if
            // the unique predicate is also in the merge map. If it is
            // then we need to get the parent associated with
            // it. Finding the equivalent tag in the parent works
            // because the UniquePredicate hash works equivalent.
            if( mergeMap.get( predicate ) != null ){
                newOutputNode = outputDocument
                    .importNode
                    ( (Element)mergeMap.get( predicate ), true );
                
                // if the unique predicate is an adaptable one, we
                // need to adapt the child. This enables the
                // modification of the final output node in a way
                // specific to the UniquePredicate
                if( predicate instanceof AdaptableUniquePredicate ){
                    
                    // Import the parent node here, which saves us
                    // from having to do it in the
                    // AdaptableUniquePredicate. 
                    Element importElement = (Element)outputDocument
                        .importNode( element, true );
                    
                    // now adapt it
                    newOutputNode = 
                        ((AdaptableUniquePredicate)predicate).adaptChild
                        ( importElement, (Element)newOutputNode );
                }
                                
                // now we want to replace the current element in the
                // output with that of the child. 
                currentOutput.appendChild( newOutputNode );
                // we only want to merge things once.
                mergeMap.remove( predicate );
                // we have done a merge, so now terminate....we do not
                // need to recurse further down
                return;
            }
        }
        
        // This recursion is probably just a bug. It can almost
        // certainly be removed by just doing a deep import here. Will
        // try this out when I get around to testing it. 
        newOutputNode = outputDocument
            .importNode( mergeNode, false );

        currentOutput.appendChild( newOutputNode );

        NodeList children = current.getChildNodes();
        int length = children.getLength();

        for( int i = 0; i < length; i++ ){
            recurseTreeForMerge( children.item( i ),
                                 newOutputNode,
                                 outputDocument,
                                 mergeMap );
        }
    }

    /**
     * Return any UniquePredicate which accepts this element type.
     */
    private UniquePredicate getUniquePredicate( Element element )
    {
        for( int i = 0; i < predicates.length; i++ ){
            if( predicates[ i ].accept( element ) ){
                return predicates[ i ].newInstance( element );
            }
        }
        return null;
    }

    /*
     * Generate a hash of top-level elements (i.e. the children of
     * the <project> element) in the document which are unique as
     * judged by the UniquePredicates of this object
     */
    private Map generateUniques( Document document )
    {
        // use a LinkedHashMap here to maintain the iteration
        // ordering. This is important, so that the output document
        // maintains its ordering, with respect to the input
        // document.
        Map retn = new LinkedHashMap();
        NodeList children = document.getDocumentElement().getChildNodes();
        
        int length = children.getLength();
        for (int i = 0; i < length; i++) {
            Node child = children.item(i);
            if (child instanceof Element) {
                // check to see whether this element is a uniqifiable
                // one or not, by testing against each predicate.
                UniquePredicate predicate = getUniquePredicate ((Element)child);
                if (predicate != null) {
                    retn.put (predicate, predicate.getElement());
                }
            }
        }

        return retn;
    }

} // MergeDocuments



/*
 * ChangeLog
 * $Log: MergeDocuments.java,v $
 * Revision 1.5  2003/09/08 11:02:32  lordp
 * Documentation.
 * Specials support.
 *
 * Revision 1.4  2003/08/19 12:42:41  sharmann
 * To avoid children of top-level elements in a merge doc being added 
 * at top level as well as in the imported element, don't recurse when
 * building the 'UniquePredicate' map
 *
 * Revision 1.3  2002/12/10 14:30:59  lordp
 * Added boilerplate
 *
 * Revision 1.2  2002/12/10 13:50:07  lordp
 * Adds all tags in child, into generated file
 *
 * Revision 1.1  2002/12/04 17:19:51  lordp
 * Initial checkin
 *
 */
