/*
 *This library 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.
 *
 *This library 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 this library; if not, write to the Free Software
 *Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* 
 * This software was written by Phillip Lord (p.lord@hgmp.mrc.ac.uk)
 * whilst at the University of Manchester as a Pfizer post-doctoral 
 * Research Fellow. 
 *
 * The initial code base is copyright by Pfizer, or the University
 * of Manchester. Modifications to the initial code base are copyright
 * of their respective authors, or their employers as appropriate. 
 * Authorship of the modifications may be determined from the ChangeLog
 * placed at the end of this file
 */

package uk.ac.man.bioinf.apps.xml; // Package name inserted by JPack
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.Properties;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import uk.ac.man.bioinf.debug.Debug;
import uk.ac.man.bioinf.module.AbstractEnumeratedModuleIdentifier;
import uk.ac.man.bioinf.module.GenericModuleIdentifier;
import uk.ac.man.bioinf.module.Module;
import uk.ac.man.bioinf.module.ModuleException;
import uk.ac.man.bioinf.module.ModuleExceptionType;
import uk.ac.man.bioinf.module.ModuleIdentifier;
import uk.ac.man.bioinf.module.ModuleFactoryInstance;


/**
 * XMLBootModule.java
 *
 * This is a module which loads other modules by parsing an XML file
 * defined by the module DTD. For itself it requires a single
 * parameter which is the file to start parsing. It can also take a
 * second parameter which defines the class to use to get the initial
 * parser instance, which should extend and override the DefaultXMLParser class. 
 * The class has been written this way because at the
 * moment neither the SAX or the DOM parser API actually defines how
 * this is done and it varies from parser to parser. 
 *
 * Once the files have been read there is really no need to hold a
 * copy of the object structure in memory so this class uses the SAX
 * parser. Ive decided to use SAX2, which is still in beta for several
 * of the publically available parsers. Hopefully this wont be a problem.
 *
 * Created: Mon May  8 20:37:57 2000
 *
 * @author Phillip Lord
 * @version $Id: XMLBootModule.java,v 1.21 2002/03/08 17:44:44 lord Exp $
 */

public abstract class XMLBootModule extends Module
{
  // actual logic for the module loading
  
  
  // these store the last information to have come through. Normally
  // this would be a pretty sloppy way of doing this, as it makes a
  // lot of assumptions about the correctness of the event stream
  // coming in, but in the case we can guarantee this simply enough
  // by turning on the XML validation.
  // This class is designed to work, not to be efficient!
  private MiniStack pcDataStack      = new MiniStack();
  private MiniStack classStack       = new MiniStack();
  private MiniStack nameStack        = new MiniStack();
  private MiniStack identifierStack  = new MiniStack();
  private MiniStack nodeStack        = new MiniStack();
  private MiniStack treeStack        = new MiniStack();
  private MiniStack valueStack       = new MiniStack();
  private MiniStack valueLastStack   = new MiniStack();
  private MiniStack paramNameStack   = new MiniStack();
  

  private boolean identifierConcrete   = false;
  private boolean readRequired         = true;
  private Properties configProperties;
  private ConfigNode configTree;
  private Object[] configObject;
  private String provideName;

  // pc data. Stuff it onto the stack
  public void characters( String chars )
  {
    pcDataStack.push( chars );
  }
   
  // move the class name on to the class stack
  public void classEnd()
  {
    classStack.push( pcDataStack.pop() );
  }
  
  // move the name onto the stack
  public void nameEnd()
  {
    nameStack.push( pcDataStack.pop() );
    valueLastStack.push( Boolean.FALSE );
  }
  
  public void paramnameEnd()
  {
    paramNameStack.push( pcDataStack.pop() );
  }
  
  public void identifierStart( Attributes attribute )
  {
    if( attribute.getValue( "concrete" ) != null ){
      identifierConcrete = attribute.getValue( "concrete" ).equals( "true" );
    }
    else{
      identifierConcrete = true;
    }
  }
  
  public void enumerationEnd()
  {
    try{
      // load the enumeration class and get all of its identifiers
      ModuleIdentifier[] ident = AbstractEnumeratedModuleIdentifier.getAllIdentifiers
	( Class.forName( (String)classStack.pop() ) );
      // stuff them into the factory
      getContext().getModuleFactory().addIdentifier( ident );
    }
    catch( ClassNotFoundException cnfe ){
      // (PENDING:- PL) Need to do something better with this. 
      if( Debug.debug )
	Debug.both( this, "Can not load enumerated identifiers", cnfe );
    }
  }
      
  public void genericEnd()
  {
    ModuleIdentifier ident;
    
    ident = new GenericModuleIdentifier
      ( (String)classStack.pop(), (String)nameStack.pop(), !identifierConcrete );
    
    getContext().getModuleFactory().addIdentifier( ident );
  }

  public void startEnd()
  {
    String name = (String)nameStack.pop();
    
    try{
      // the get method ensures that start has been called
      getContext().getModuleFactory().getModule( getContext().getModuleFactory().resolveModuleName( name ) );
    }
    catch( ModuleException mod ){
      // I don't really want this to be fatal....
      mod.printStackTrace();
    }
  }
  
  public void loadEnd() throws SAXException
  {
    try{
      String moduleName = (String)nameStack.pop();
      ModuleIdentifier ident  = getContext().getModuleFactory().resolveModuleName( moduleName );
      if( ident == null ) {
	throw new ModuleException( "Could not resolve name " + moduleName, 
				   ModuleExceptionType.MODULE_RESOLVE_FAIL );
      }
      getContext().getModuleFactory().load( ident );
      
    }
    catch( ModuleException me ){
      // (PENDING:- PL) This should probably be a lot better!
      me.printStackTrace();
    }
  }
  
  public void readStart( Attributes attribute )
  {
    if( attribute.getValue( "required" ) != null ){
      readRequired = attribute.getValue( "required" ).equals( "true" );
    }
    else{
      readRequired = true;
    }
  }
    
  public void readEnd() throws SAXException
  {
    String parse = (String)pcDataStack.pop();
    parseResource( parse, readRequired );
  }

  public void valueEnd()
  {
    valueStack.push( pcDataStack.pop() );
    valueLastStack.push( Boolean.TRUE );
  }

  public void paramEnd()
  {
    configProperties.setProperty
      ( (String)paramNameStack.pop(), 
	(String)((((Boolean)valueLastStack.pop()).booleanValue()) 
		 ? ("VAL:" + valueStack.pop()) : ("MOD:" + nameStack.pop()) ) );
  }
  
  public void propertiesStart()
  {
    if( configObject[ 0 ] == null ){
      configProperties = new Properties();
    }
    else{
      configProperties = (Properties)configObject[ 0 ];
    }
  }
  
  public void propertiesEnd()
  {
    configObject[ 0 ] = configProperties;
    configProperties = null;
  }
  
  public void provideEnd()
  {
    provideName = (String)nameStack.pop();
  }
  
  public void configStart( Attributes attrib )
  {
    String val =  attrib.getValue( "mode" );
    
    if( val == null || val.equals( "add" ) ){
      ModuleIdentifier ident = getContext().getModuleFactory().resolveModuleName( (String)nameStack.peek() );
      if( ident != null ){
        configObject = (Object[])getContext().getModuleFactory().getConfig( ident );
      }
    }
    
    if( configObject == null ){
      configObject = new Object[ 2 ];
    }
  }
  
  public void moduleEnd()
  {
    ModuleFactoryInstance fact = getContext().getModuleFactory();
    
    ModuleIdentifier ident = fact.resolveModuleName( (String)nameStack.pop() );
    
    if( provideName != null ){
      // if the provide name is not null, then this Module should in
      // fact be delegating its functionality to elsewhere, because
      // its an interface module. So we need to associate the module
      // providing this implementation with the identifier.
      fact.setConcreteIdentifier( ident, fact.resolveModuleName( provideName ) );
      provideName = null;
    }
    
    getContext().getModuleFactory().setConfig( ident, configObject );
    configObject = null;
  }
  
  public void treeStart()
  {
    if( configObject[ 1 ] == null ){
      configTree = new ConfigNode();
    }
    else{
      configTree = (ConfigNode)configObject[ 1 ];
    }
  }
  
  public void nodeStart()
  {
    nodeStack.push( new ConfigNode() );
  }
  
  public void nodeEnd()
  {
    // get the last node
    ConfigNode last = (ConfigNode)nodeStack.pop();
    
    // set its value correctly. 
    if( ((Boolean)valueLastStack.pop()).booleanValue() ){
      last.setData( (String)valueStack.pop() );
    }
    else{
      last.setData( getContext().resolveModuleName( (String)nameStack.pop() ) );
    }
    
    // now stuff this node into the node above if it exists, or the
    // configTree if it doesnt. 
    if( nodeStack.peek() == null ){
      configTree.addChildNode( last );
    }
    else{
      ((ConfigNode)nodeStack.peek()).addChildNode( last );
    }
  }
  
  public void treeEnd()
  {
    configObject[ 1 ] = configTree;
    configTree = null;
  }
  
  public void load() throws ModuleException
  {
    try{
      parseResource( fetchMainBootName(), true );
      
      // let all this stuff get gc'd
      pcDataStack      =
        classStack       =
        nameStack        =
        identifierStack  =
        nodeStack        =
        treeStack        =
        valueStack       =
        valueLastStack   =
        paramNameStack   = null;
      
      configObject     = null;
      configTree       = null;
      configProperties = null;
    }
    catch( SAXException sax ){
      throw new ModuleException( "Problem parsing file ", ModuleExceptionType.MODULE_INIT_PROBLEM, sax );
    }
  }
  
  public void parseResource( String name, boolean required ) throws SAXException
  {
    try{
      parseStream( resolveXMLLoadName( name ) );
    }
    catch( FileNotFoundException fnfe ){
      // throw this if required...
      if( required ){
	throw new SAXException( "IO Problem with file " + name, fnfe );
      }
    }
    catch( IOException io ){
      throw new SAXException( "IO Problem with file " + name, io );
    }
  }
  
  public void parseStream( InputStream stream ) throws IOException, SAXException
  {
    try{
      
      XMLReader parser = getXMLParser();
      //parser.setFeature("http://xml.org/sax/features/validation", true);
      
      XMLBootParserListener listener = new XMLBootParserListener( this );
      
      parser.setContentHandler( listener );
      
      //parser.setErrorHandler( listener );
      InputSource inp =
	( new InputSource( stream ) );
      inp.setSystemId
	( fetchModuleSystemIdentifier() );
      
      parser.parse( inp );
    }
    catch( SAXException se ){
      if( Debug.debug ){
	Debug.both( this, "Sax exception during parsing", se );
	Debug.both( this, "Original Exception was", se.getException() );
      }
      else{
	throw se;
      }
    }
  }
  
  public String getVersion()
  {
    return "$Id: XMLBootModule.java,v 1.21 2002/03/08 17:44:44 lord Exp $";
  }
  
  private XMLReader getXMLParser()
  {
    try{
      XMLParserFactory parserGen = 
	(XMLParserFactory)Class.forName( getXMLParserClass() ).newInstance();
      return parserGen.getXMLParser();
    }
    catch( Exception exp ){
      if( Debug.debug )
	Debug.throwable( this, exp );
    }
    return null;
  }
  
  protected String getXMLParserClass()
  {
    // (PENDING:- PL) This will be configurable
    return DefaultXMLParser.class.getName();
  }
  
  public abstract String fetchMainBootName();
  
  public abstract String fetchModuleSystemIdentifier();
  
  public abstract InputStream resolveXMLLoadName( String name ) throws IOException;
  
  // dig dem funky method names
  public class MiniStack 
  {
    private LinkedList list = new LinkedList();
    public void push( Object object )
    {
      list.addFirst( object );
    }
    
    public Object peek()
    {
      if( list.size() == 0 ) return null;
      return list.getFirst();
    }
    
    public Object pop()
    {
      if( list.size() == 0 ) return null;
      return list.removeFirst();
    }
  }

} // XMLBootModule



/*
 * ChangeLog
 * $Log: XMLBootModule.java,v $
 * Revision 1.21  2002/03/08 17:44:44  lord
 * Switch off validation
 *
 * Revision 1.20  2001/05/08 15:53:06  lord
 * Set validation to true
 *
 * Revision 1.19  2001/04/11 17:04:42  lord
 * Added License agreements to all code
 *
 * Revision 1.18  2001/01/31 17:53:49  lord
 * Changes due to removal of InterfaceIdentifier
 * More exception handling update. This class is due
 * for a complete overhaul in its exception handling.
 *
 * Revision 1.17  2001/01/26 17:11:46  lord
 * Improved exception handling.
 * Loading config from multiple places
 *
 * Revision 1.16  2001/01/19 19:47:45  lord
 * Removed debug
 *
 * Revision 1.15  2001/01/15 18:42:56  lord
 * Support for the read required attribute
 * Improved the exception handling. Can not give an informative stack
 * trace when an exception is thrown.
 *
 * Revision 1.14  2000/10/19 17:51:52  lord
 * Import rationalisation.
 * Improved error handling, so that this class does not swallow errors
 * when Debug.debug is false.
 *
 * Revision 1.13  2000/09/25 16:35:34  lord
 * Changes made so that the XMLParser used is no longer hard coded
 * but comes from a factory. This allows for instance giving the parser a
 * custom entity resolver.
 *
 * Revision 1.12  2000/09/15 17:24:42  lord
 * No longer uses the ModuleFactory.
 *
 * Revision 1.11  2000/08/03 16:38:24  lord
 * Modifications to enable Cinema to run from jar file
 *
 * Revision 1.10  2000/08/01 17:11:41  lord
 * Updated support for GenericModuleIdentifier
 *
 * Revision 1.9  2000/07/26 13:36:01  lord
 * Modified due to name change for AbstractEnumeratedModuleIdentifier
 *
 * Revision 1.8  2000/07/18 11:07:44  lord
 * Cosmetic
 *
 * Revision 1.7  2000/06/27 15:57:58  lord
 * Improved documentation
 *
 * Revision 1.6  2000/06/05 14:19:22  lord
 * Have neatened up the way the node tree is built. I actually
 * understand the code that I have written now!
 *
 * Revision 1.5  2000/05/30 16:55:22  lord
 * Horrible mess up in last checkin
 *
 * Revision 1.4  2000/05/30 16:21:55  lord
 * Many changes. Imports. Added support for more elements.
 * Fixed dubious mechanism for module configuration.
 * Class now abstract to allow subclass to locate the XML files it
 * needs. Changes due to completion of module architecture.
 *
 * Revision 1.3  2000/05/24 15:38:03  lord
 * MiniStack does not need to extend LinkedList
 *
 * Revision 1.2  2000/05/18 17:09:21  lord
 * Lots of new methods
 *
 * Revision 1.1  2000/05/15 16:23:53  lord
 * Initial checkin
 *
 */
