/*
 * XMLIOManager.java
 *
 * Created on October 16, 2001, 8:03 AM
 */
package org.freehep.xml.io;
import java.util.*;
import org.jdom.*;

/**
 * The XMLIOManager is in charge of saving and restoring any object
 * configuration through XML. It internally assigns objects and unique
 * identification string used for cross-reference among xml nodes.
 *
 * @author  turri
 * @version 1.0
 *
 */

public class XMLIOManager {
    
    private Hashtable objIdHash = new Hashtable();
    private Hashtable idHash    = new Hashtable();
    private Hashtable nodeHash  = new Hashtable();
    private Hashtable classHash = new Hashtable();
    private Hashtable refTable  = new Hashtable();

    private XMLIORegistry xmlioRegistry;
    private XMLIOStreamManager xmlioStreamManager;
    
    private boolean useId = true;
    
    /**
     * The basic constructor.
     * @param xmlioRegistry    The XMLIORegistry from which the XMLIOManager
     *                         gets the XMLIOFactories and the XMLIOProxies to
     *                         save and restore objects.
     * @param xmlioStreamManager The xmlioStreamManager that deals with the input/output file.
     *
     */
    
    private XMLIOManager() {
        this( new DefaultXMLIORegistry(), new XMLIOFileManager("defaultXMLIOFile.xml"), true);
    }

    public XMLIOManager(String fileName) {
        this( new DefaultXMLIORegistry(), new XMLIOFileManager(fileName), true);
    }

    private XMLIOManager( boolean useId ) {
        this( new DefaultXMLIORegistry(), new XMLIOFileManager("defaultXMLIOFile.xml"), useId);
    }
    
    private XMLIOManager( XMLIORegistry xmlioRegistry, XMLIOStreamManager xmlioFileManager ) {
        this( xmlioRegistry, xmlioFileManager, true);
    }
    
    private XMLIOManager( XMLIORegistry xmlioRegistry, XMLIOStreamManager xmlioFileManager, boolean useId ) {
        setXMLIORegistry( xmlioRegistry );
        setXMLIOStreamManager( xmlioFileManager );
        this.useId = useId;
    }
    
    protected void setXMLIORegistry( XMLIORegistry xmlioRegistry ) {
        this.xmlioRegistry = xmlioRegistry;
    }
    
    /**
     * Get the XMLIORegistry.
     * @return the XMLIORegistry
     *
     */
    public XMLIORegistry getXMLIORegistry() {
        return xmlioRegistry;
    }
    
    public void setXMLIOStreamManager( XMLIOStreamManager xmlioStreamManager ) {
        this.xmlioStreamManager = xmlioStreamManager;
    }
    
    public XMLIOStreamManager getXMLIOStreamManager() {
        return xmlioStreamManager;
    }
    
    /**
     * Restore an object from an xml node.
     * @param nodeEl The node containing the object's information.
     *               This method should be used by the user to restore
     *               nested objects within the restore method of an object
     *               implementing the XMLIO interface.
     * @return       The restored object. The object is first created by
     *               invoking the create method on the corresponding XMLIOFactory.
     *               Once created the restore method is invoked either on the object
     *               itself (if it implements the XMLIO interface) or on the appropriate
     *               XMLIOProxy.
     *
     */
    public Object restore( Element nodeEl ) {
        String objIdRef = (String) nodeEl.getAttributeValue( "objIdRef" );
        if ( objIdRef != null ) 
            if ( useId )
                if ( isObjIdRegistered( objIdRef ) )
                    return getObj( objIdRef );
                else
                    return restore( (Element) refTable.get(objIdRef) );
        
        String objId = (String) nodeEl.getAttributeValue( "objId" );
        if ( useId && isObjIdRegistered( objId ) ) return getObj( objId );
        
        Class objClass;
        if ( classHash.containsKey( nodeEl.getName() ) )
            objClass = (Class) classHash.get( nodeEl.getName() );
        else
            objClass = xmlioRegistry.getIdClass(nodeEl.getName());
        
        Object xmlio = getXMLIOFactory( objClass ).createObject( objClass );
        if ( useId ) registerObj( xmlio, objId );
        if ( xmlio instanceof XMLIO  ) ((XMLIO)xmlio).restore( this, nodeEl );
//        else getXMLIOProxy( xmlio.getClass() ).restore( xmlio, this, nodeEl);
        else getXMLIOProxy( objClass ).restore( xmlio, this, nodeEl);
        return xmlio;
    }
    
    /**
     * Save an object to an XML node.
     * @param obj The object to be saved.
     * @return    The jdom Element containing the object's unique
     *            identification string (in the attribute "objId") and
     *            whose name is the object's identifier. The user will have
     *            to append to this node all the attributes and other
     *            jdom elemets required to save the object's configuration.
     *
     */
    public Element saveAs( Object obj, Class clazz ) {
        String nodeName;
        if ( nodeHash.containsKey( clazz ) ) nodeName = (String) nodeHash.get(clazz);
        else nodeName = (String) xmlioRegistry.getClassId( clazz );
        Element el = new Element( nodeName );
        
        if ( isObjRegistered( obj ) ) el.setAttribute( "objIdRef", getObjId( obj ) );
        else {
            if ( useId ) el.setAttribute( "objId", registerObj( obj ) );
            if ( obj instanceof XMLIO ) ((XMLIO)obj).save( this, el );
            else getXMLIOProxy( clazz ).save( obj, this, el );
        }
        return el;
    }

    public Element save( Object obj ) {
        return saveAs( obj, obj.getClass() );
    }
    
    /**
     * The main method to save an array of objects to XML.
     * @param objs The array of objects to be saved.
     *
     */
    public void saveToXML( Object[] objs, Element nodeEl ) {
        resetObjId();
        for ( int i = 0; i < objs.length; i++ ) {
            nodeEl.addContent( save( objs[i] ) );
        }
        try {
            xmlioStreamManager.saveRootElement( nodeEl );
        } catch ( java.io.IOException ioe ) {
            System.out.println(" Problem with file "+ioe.getMessage());
        }
    }
    
    /**
     * The main method to restore from xml.
     * @return The array of the restored objects.
     *
     */
    public Object[] restoreFromXML() {
        resetObjId();
        Object[] objs = null;
        try {
            Element restoreElement = xmlioStreamManager.getRootElement();
            //Create the list of references here.
            List children = restoreElement.getChildren();
            for (Iterator it = children.iterator(); it.hasNext(); )
                loadRefTable( (Element) it.next() );
            
            objs = new Object[ children.size() ];
            int child = 0;
            for (Iterator it = children.iterator(); it.hasNext(); )
                objs[ child++ ] = restore( (Element) it.next() );
        } catch ( JDOMException je ) {
            System.out.println(" Problem with restoring "+je.getMessage());
        } catch ( java.io.IOException ioe ) {
            System.out.println(" Problem with file "+ioe.getMessage());
        }
        return objs;
    }
    
    /**
     * Assign to a Class a string identifier.
     * @param clName The Class.
     * @param id     The new identifier for the class.
     *
     */
    public void setClassId(Class clName, String id) {
        nodeHash.put(clName, id);
        classHash.put(id, clName);
    }
    
    protected void loadRefTable(Element el) {
        String objId = el.getAttributeValue("objId");
        if ( objId != null ) 
            refTable.put( objId, el );
        List children = el.getChildren();
        for (Iterator it = children.iterator(); it.hasNext(); )
                loadRefTable( (Element) it.next() );
    }
    
    /**
     * Down here are only private and protected methods.
     *
     */
    
    /**
     * Create the object's unique Id for cross-reference among xml nodes
     * @param obj the object.
     * @return    the object's unique Id.
     */
    private String makeObjId( Object obj ) {
        String clName  = obj.getClass().getName();
        String pkgName = obj.getClass().getPackage().getName();
        String idName  = clName.substring( pkgName.length()+1, clName.length() ).trim();
        while ( idName.indexOf('$') != -1 )
            idName  = idName.substring( idName.indexOf('$')+1, idName.length() ).trim();
        int i = 1;
        if ( idHash.containsKey( idName ) ) i = ( (Integer)( idHash.get( idName ) ) ).intValue() + 1;
        idHash.put( idName, new Integer( i ) );
        String objId = idName+i;
        return objId;
    }
    
    /**
     * Register object assigning an unique Id for cross-reference among xml nodes
     * @param obj the object to be registered.
     * @return    the object's unique Id.
     */
    private String registerObj( Object obj ) {
        if ( objIdHash.contains( obj ) ) return getObjId( obj );
        String objId = makeObjId( obj );
        objIdHash.put( objId, obj );
        return objId;
    }
    
    /**
     * Register object with a pre-assigned Id for cross-reference among xml nodes
     * @param obj   the object to be registered
     * @param objId the object's Id
     */
    private void registerObj( Object obj, String objId ) {
        if ( objIdHash.containsKey( objId ) ) return;
        objIdHash.put( objId, obj);
    }
    
    /**
     * Get the object's unique Id for cross-reference among xml nodes
     * @param obj the object whose unique id we are looking for
     * @return    the unique String Id of object <code>obj</code>
     *            if no string is found the object itself is returned
     */
    private String getObjId( Object obj ) {
        for ( Enumeration keys = objIdHash.keys(); keys.hasMoreElements(); ) {
            String key = (String) ( keys.nextElement() );
            if ( objIdHash.get( key ).equals( obj ) ) return key;
        }
        return (String)obj;
    }
    
    /**
     * Get the object corresponding to unique Id for cross-reference among xml nodes
     * @param objId the object's unique String Id
     * @return      the object corrisponding to unique Id <code>objId</code>
     *              if the object is registered or the string <code>objId</code>
     *              if the object is not registered
     *
     */
    private Object getObj( String objId ) {
        if ( objIdHash.containsKey( objId ) )
            return objIdHash.get( objId );
        return objId;
    }
    
    /**
     * Check if an object is registered
     * @param obj the object to be checked
     * @return <code>true</code> if the object is registered
     *         <code>false</code> otherwise
     *
     */
    private boolean isObjRegistered( Object obj ) {
        if ( objIdHash.contains( obj ) ) return true;
        return false;
    }
    
    /**
     * Check if an unique Id is registered
     * @param objId the unique Id to be checked
     * @return <code>true</code> if the unique Id is registered
     *         <code>false</code> otherwise
     *
     */
    private boolean isObjIdRegistered( String objId ) {
        if ( objIdHash.containsKey( objId ) ) return true;
        return false;
    }
    
    /**
     * Reset the stored ids
     */
    public void resetObjId() {
        objIdHash.clear();
        idHash.clear();
        refTable.clear();
    }
    
    /**
     * Get the XMLIOFactory to restore the xml node
     * @param nodeEl the node to be restored
     * @return  the XMLIOFactory
     *
     */
    protected XMLIOFactory getXMLIOFactory( Class objClass ) {
        return xmlioRegistry.getXMLIOFactory( objClass );
    }
    
    protected XMLIOProxy getXMLIOProxy( Class objClass ) {
        return xmlioRegistry.getXMLIOProxy( objClass );
    }
    
    
}



