// ResourceIndexer.java
// $Id: ResourceIndexer.java,v 1.20 1997/01/23 10:17:23 abaird Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.jigsaw.indexer ;

import java.util.* ;
import java.io.* ;

import w3c.tools.store.*;
import w3c.jigsaw.resources.* ;
import w3c.jigsaw.http.*;
import w3c.www.mime.* ;

/**
 * Jigsaw indexer.
 * The indexer is an object that given some global configuration informations, 
 * tries to build default resources for files that the server doesn't know
 * about.
 */

public class ResourceIndexer implements ResourceStoreHolder {
    private static boolean inited = false ;
    private static final boolean bootstrap = false;
    private static final boolean extCaseSensitive = false;
    /**
     * Our extension database repository.
     */
    protected File extrep = null ;
    /**
     * our directory database repository.
     */
    protected File dirrep = null ;
    /**
     * Our extensions database.
     */
    protected ResourceStore extensions = null ;
    /**
     * Our directory templates database.
     */
    protected ResourceStore directories = null ;

    /**
     * The server that's using us.
     */
    protected httpd server = null ;

    /**
     * Acquire the extension store.
     * Should only be called from a synchronized method.
     */

    protected synchronized void acquireExtensionsStore() {
	if ( extensions == null ) {
	    ResourceStoreManager man = server.getResourceStoreManager() ;
	    extensions = man.loadResourceStore(this, extrep);
	}
	return ;
    }

    /**
     * Acquire the directories store.
     * Should only be called from a synchronized method.
     */

    protected synchronized void acquireDirectoriesStore() {
	if ( directories == null ) {
	    ResourceStoreManager man = server.getResourceStoreManager() ;
	    directories = man.loadResourceStore(this, dirrep);
	}
	return;
    }

    /**
     * Load an extension descriptor.
     * @param ext The name of the extension.
     * @return An instance of Extension, or <strong>null</strong>.
     */

    public synchronized HTTPResource loadExtension (String name) {
	acquireExtensionsStore() ;
	// Bootstraping only, should be left commented.
	if ( bootstrap && ! inited ) {
	    inited = true ;
	    // Add an entry for html files:
	    if ( loadExtension("html") == null ) {
		HTTPResource r = new FileResource();
		r.setValue("identifier", "html");
		r.setValue("content-type", MimeType.TEXT_HTML) ;
		extensions.addResource(r);
	    }
	    // Add the extensions editor for bootstrapping:
	    if ( loadExtension("fun") == null ) {
		HTTPResource e = new ExtensionsEditor();
		e.setValue("identifier", "fun");
		extensions.addResource(e);
	    }
	}
	try {
	    String key = extCaseSensitive ? name : name.toLowerCase();
	    return (HTTPResource) extensions.loadResource(name, null) ;
	} catch (InvalidResourceException ex) {
	    String msg = ("[resource indexer]: extensions \""
			  + name
			  + "\" couldn't be restored ("+ex.getMessage()+")");
	    server.errlog(msg);
	    // We don't remove the resource from the store, since this may
	    // be only a temporary problem
	}
	return null;
    }

    /**
     * Save your state.
     */

    public void save() {
	try {
	    if ( extensions != null )
		extensions.save() ;
	    if ( directories != null )
		directories.save() ;
	} catch (Exception ex) {
	    ex.printStackTrace() ;
	}
    }

    /**
     * Register a new extension to the store.
     * @param e The extension to register.
     */

    public synchronized void registerExtension(HTTPResource e) {
	acquireExtensionsStore() ;
	extensions.addResource(e) ;
    }

    /**
     * Unregister knowledge about a given extension.
     * @param ext The extension to unregister.
     */

    public synchronized void unregisterExtension(String ext) {
	acquireExtensionsStore() ;
	extensions.removeResource(ext) ;
    }

    /**
     * Get the list of extension names.
     * @return An enumeration of the known extension names.
     */

    public Enumeration enumerateExtensionNames() {
	acquireExtensionsStore() ;
	return extensions.enumerateResourceIdentifiers() ;
    }

    /**
     * Return the class (if any) that our store defines for given extension.
     * @param ext The extension we want a class for.
     * @return A Class instance, or <strong>null</strong>.
     */

    protected HTTPResource getTemplateFor(String ext) {
	HTTPResource template = loadExtension(ext) ;
	if ( template != null ) {
	    HTTPResource check = new HTTPResource();
	    if (template.getClass() == check.getClass())
		template = null;
	}
	return template;
    }

    /**
     * Merge the attributes this extension defines, with the provided ones.
     * @param attrs The attributes we want to fill with default values.
     * @param ext The extension name.
     * @param into The already built set of default values.
     * @return A Hashtable, containing the augmented set of default attribute
     *    values.
     */

    protected Hashtable mergeDefaultAttributes(HTTPResource template
					       , String ext
					       , Hashtable into) {
	Attribute    attrs[] = template.getAttributes();
	HTTPResource e       = loadExtension(ext) ;
	if ( e != null ) {
	    for (int i = 0 ; i < attrs.length ; i++) {
		if ( ! template.definesAttribute(i) ) {
		    int idx = e.lookupAttribute(attrs[i].getName());
		    if ( idx >= 0 ) {
			Object value = e.getValue(idx, null);
			if ( value != null )
			    into.put(attrs[i].getName(), value) ;
		    }
		}
	    }
	}
	return into ;
    }

     
    /**
     * Get this name's extensions.
     * @param name The file name.
     * @return An array of string, giving ach (parsed) extension, or
     *    <strong>null</strong> if none was found.
     */

    private final static String noextension[] = { "*noextension*" } ;
    protected String[] getFileExtensions(String name) {
	Vector items = new Vector() ;
	int dpos     = name.indexOf ('.') ;
	
	if ( dpos > 0 ) {
	    int pos = dpos+1 ;
	    while ( (dpos = name.indexOf ('.', pos)) != -1 ) {
		// Skip empty extension:
		if ( dpos == pos+1 ) { 
		    pos++ ;
		    continue ;
		}
		// Else add new extension:
		items.addElement (name.substring(pos, dpos)) ;
		pos = dpos + 1;
	    }
	    if ( pos < name.length() )
		items.addElement (name.substring(pos)) ;
	    String exts[] = new String[items.size()] ;
	    items.copyInto (exts) ;
	    return exts ;
	} else {
	    // That file has no extensions, we'll use '.' as its extension
	    return noextension;
	}
    }

    /**
     * Create a default file resource for this file (that exists).
     * @param directory The directory of the file.
     * @param name The name of the file.
     * @param defs A set of default attribute values.
     * @return An instance of HTTPResource, or <strong>null</strong> if
     *    we were unable to create it.
     */

    protected HTTPResource createFileResource(File directory
					      , String name
					      , Hashtable defs) {
	File         file     = new File(directory, name) ;
	HTTPResource template = null;
	
	// Check that at least one class is defined for all the extensions:
	String exts[] = getFileExtensions(name) ;
	if ( exts == null )
	    return null ;
	for (int i = exts.length-1 ; i >= 0 ; i--) {
	    template = getTemplateFor(exts[i]) ;
	    if ( template != null )
		break ;
	}
	if ( template == null )
	    return null ;
	// Create the runtime-time default values for attributes.
	if ( defs == null )
	    defs = new Hashtable(5) ;
	defs.put("directory", directory) ;
	defs.put("identifier", name) ;
	// Merge with values defined by the extension:
	for (int i = exts.length ; --i >= 0 ; ) 
	    mergeDefaultAttributes(template, exts[i], defs) ;
	// Create, initialize and return the new resouce
	try {
	    return (HTTPResource) template.getClone(defs);
	} catch (Exception ex) {
	    ex.printStackTrace() ;
	    return null ;
	}
    }

    /**
     * Register this new directory template into our directory store.
     * @param template The directory template.
     */

    public synchronized void registerDirectory(Directory directory) {
	acquireDirectoriesStore() ;
	directories.addResource(directory) ;
    }

    /**
     * Unregister the given directory from our stor.
     * @param name The name of the directory to unregister.
     */

    public synchronized void unregisterDirectory(String id) {
	acquireDirectoriesStore() ;
	directories.removeResource(id) ;
    }

    /**
     * Enumerate the names of the defined directory templates.
     * @return An enumeration of directory names.
     */

    public synchronized Enumeration enumerateDirectoryNames() {
	acquireDirectoriesStore() ;
	return directories.enumerateResourceIdentifiers() ;
    }

    /**
     * Load a given directory template from the store.
     * @param name The name of the template to load.
     * @return An instance of Directory, or <strong>null</strong>.
     */

    Hashtable dirdefs = null;
    public synchronized Directory loadDirectory(String name) {
	acquireDirectoriesStore() ;
	if ( dirdefs == null ) 
	    dirdefs = new Hashtable(5);
	dirdefs.put("server", server);
	try {
	    return (Directory) directories.loadResource(name, dirdefs) ;
	} catch (InvalidResourceException ex) {
	    // Emit an error message, and remove it !
	    String msg = ("[resource indexer]: directory template \""
			  + name + "\" couldn't be restored. It has "
			  + "been removed.");
	    server.errlog(msg);
	}
	return null;
    }
    
    /**
     * Create a default container resource for this directory (that exists).
     * @param directory The parent directory.
     * @param name The name of its sub-directory to index.
     * @param defaults A set of default atribute values.
     * @return An HTTP instance, or <strong>null</strong> if
     *    the indexer was unable to build a default resource for the directory.
     */

    protected HTTPResource createDirectoryResource(File directory
						   , String name
						   , Hashtable defs) {
	// Lookup the directory path, for an existing template.
	File         dir      = new File(directory, name) ;
	String       part     = null ;
	Directory    template = loadDirectory(name) ;
	HTTPResource dirtempl = null;

	if (template == null) {
	  loop:
	    while ( true ) {
		// Go up one level:
		part = dir.getName() ;
		if ( part != null ) {
		    template = loadDirectory(part) ;
		    if ((template != null)
			&& template.getGenericFlag())
			break loop ;
		} else {
		    break loop ;
		}
		String parent = dir.getParent() ;
		if ( parent != null )
		    dir  = new File(dir.getParent()) ;
		else
		    break loop ;
	    }
	}
	// If no template available, default to a raw DirectoryResource
	if ((template == null) || (template.getTemplate() == null)) {
	    try {
		Class c = null;
		c = Class.forName("w3c.jigsaw.resources.DirectoryResource");
		dirtempl = (HTTPResource) c.newInstance();
	    } catch (Exception ex) {
		return null;
	    }
	} else {
	    dirtempl = template.getTemplate();
	}
	// Clone the appropriate template:
	File file = new File(directory, name) ;
	defs.put("directory", file) ;
	defs.put("identifier", name) ;
	defs.put("server", server);
	try {
	    return (HTTPResource) dirtempl.getClone(defs);
	} catch (Exception ex) {
	    ex.printStackTrace() ;
	    return null ;
	}
    }

    /** 
     * Resource store holder implementation - get rid of our store.
     * @param store The store we should get rid of.
     * @return Always <strong>true</strong>.
     */

    public synchronized boolean acceptStoreUnload(ResourceStore store) {
	return true ;
    }

    /**
     * Resource store holder implementation - Shutdown our store.
     * @param store The store we should shut down.
     */

    public synchronized void notifyStoreShutdown(ResourceStore store) {
	if ( store == extensions ) {
	    extensions = null ;
	} else if ( store == directories ) {
	    directories = null ;
	}
    }

    /**
     * Resource store holder implementation - Save the store.
     * @param store The store to be saved.
     * @return A boolean <strong>true</strong> if success.
     */

    public void notifyStoreStabilize(ResourceStore store) {
	return ;
    }

    /**
     * Try to create a resource for the given file.
     * This method makes its best efforts to try to build a default
     * resource out of a file. 
     * @param directory The directory the file is in.
     * @param name The name of the file.
     * @param defs Any default attribute values that should be provided
     *    to the created resource at initialization time.
     * @return A Resource instance, or <strong>null</strong> if the given
     *    file can't be truned into a resource given our configuration
     *    database.
     */

    public HTTPResource createResource(File directory
				       , String name
				       , Hashtable defs) {
	// Does this file exists ?
	File file = new File(directory, name) ;
	if ( ! file.exists() )
	    return null ;
	// Okay, dispatch on wether it is a file or a directory.
	if ( file.isDirectory() )
	    return createDirectoryResource(directory, name, defs) ;
	else
	    return createFileResource(directory, name, defs) ;
    }

    /**
     * Create a new resource  indexer object.
     */

    public ResourceIndexer(httpd server) {
	this.server     = server ;
	this.extrep = new File(server.getConfigDirectory(), "exts.db");
	this.dirrep = new File(server.getConfigDirectory(), "dirs.db") ;
    }
}
