// SampleResourceIndexer.java
// $Id: SampleResourceIndexer.java,v 1.9 1998/01/20 16:03:17 ylafon Exp $  
// (c) COPYRIGHT MIT and INRIA, 1997.
// 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.*;

class SampleIndexerEnumeration implements Enumeration {
    private static final String list[] = {
	"directories",
	"extensions"
    };

    int idx = 0;
    
    public boolean hasMoreElements() {
	return idx < list.length;
    }

    public Object nextElement() {
	if ( idx >= list.length )
	    throw new NoSuchElementException("SampleResourceIndexer enum");
	return list[idx++];
    }

    SampleIndexerEnumeration() {
	this.idx = 0;
    }

}

/**
 * A container for directories and templates.
 */

public class SampleResourceIndexer extends Resource
    implements ContainerInterface, ResourceIndexer
{
    private static final boolean extCaseSensitive = false;
    private static final String  defname          = "*default*";


    /**
     * Attribute index - Date of last modification.
     */
    protected static int ATTR_LAST_MODIFIED = -1;
    /**
     * Attribute index - the context (a nasty hack)
     */
    protected static int ATTR_CONTEXT = -1;
    /**
     * Attribute index - the super indexer, if any.
     */
    protected static int ATTR_SUPER_INDEXER = -1;

    static {
	Attribute a = null;
	Class     c = w3c.jigsaw.indexer.SampleResourceIndexer.class;
	// Last modification date:
	a = new LongAttribute("last-modified"
			      , new Long(-1)
			      , Attribute.COMPUTED);
	ATTR_LAST_MODIFIED = AttributeRegistry.registerAttribute(c, a);
	// Our runtime context:
	a = new ObjectAttribute("context"
				, "w3c.jigsaw.resources.ResourceContext"
				, null
				, Attribute.COMPUTED|Attribute.DONTSAVE);
	ATTR_CONTEXT = AttributeRegistry.registerAttribute(c, a) ;
	// Our super indexer:
	a = new StringAttribute("super-indexer"
				, null
				, Attribute.EDITABLE);
	ATTR_SUPER_INDEXER = AttributeRegistry.registerAttribute(c, a);
	
    }

    protected SampleContainer directories = null;
    protected SampleContainer extensions  = null;

    protected ResourceContext getContext() {
	return (ResourceContext) getValue(ATTR_CONTEXT, null);
    }

    protected synchronized SampleContainer getDirectories() {
	if ( directories == null ) {
	    String diridxid = getIdentifier()+"-d";
	    directories = new TemplateContainer(getContext(), diridxid+".db");
	}
	return directories;
    }

    protected synchronized SampleContainer getExtensions() {
	if ( extensions == null ) {
	    String extidxid = getIdentifier()+"-e";
	    extensions = new TemplateContainer(getContext(), extidxid+".db");
	}
	return extensions;
    }

    public long lastModified() {
	return getLong(ATTR_LAST_MODIFIED, -1);
    }

    public String getSuperIndexer() {
	return getString(ATTR_SUPER_INDEXER, null);
    }

    public synchronized void notifyResourceLockBreak(Resource r) {
	if ( r == directories ) {
	    directories = null;
	} else if ( r == extensions ) {
	    extensions = null;
	}
    }

    public boolean hasResourceStore() {
	return false;
    }

    public ResourceStore getResourceStore(ResourceStoreHolder holder) {
	return null;
    }

    public Enumeration enumerateResourceIdentifiers(boolean all) {
	return new SampleIndexerEnumeration();
    }

    public Resource lookup(String name) {
	if ( name.equals("directories") ) {
	    return getDirectories();
	} else if ( name.equals("extensions") ) {
	    return getExtensions();
	} 
	return null;
    }

    public synchronized void delete() {
	// Remove the two stores we are responsible for:
	getExtensions().delete();
	getDirectories().delete();
	super.delete();
    }

    public void delete(String name) {
	throw new RuntimeException("static container");
    }

    public void registerResource(String name
				 , Resource resource
				 , Hashtable defs) {
	throw new RuntimeException("static container");
    }

    /*
     * 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) {
	// Bootstraping only, should be left commented.
	try {
	    String key = extCaseSensitive ? name : name.toLowerCase();
	    return (HTTPResource) getExtensions().lookup(key) ;
	} catch (InvalidResourceException ex) {
	    String msg = ("[resource indexer]: extensions \""
			  + name
			  + "\" couldn't be restored ("+ex.getMessage()+")");
	    getContext().getServer().errlog(msg);
	    // We don't remove the resource from the store, since this may
	    // be only a temporary problem
	}
	return null;
    }

      /**
     * 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 ) {
	    // Look for a default template:
	    if ((template = loadExtension(defname)) == null)
		return null ;
	}
	// Create the runtime-time default values for attributes.
	if ( defs == null )
	    defs = new Hashtable(5) ;
	if ( defs.get("directory") == null )
	    defs.put("directory", directory) ;
	if ( defs.get("identifier") == null )
	    defs.put("identifier", name) ;
	if ( defs.get("context") == null )
	    defs.put("context", getContext());
	// 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 ;
	}
    }

      /**
     * 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>.
     */

    public synchronized HTTPResource loadDirectory(String name) {
	try {
	    return (HTTPResource) getDirectories().lookup(name) ;
	} 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.");
	    getContext().getServer().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) ;
	HTTPResource dirtempl = null;

	dirtempl = loadDirectory(name);
	// If no template available, default to a raw DirectoryResource
	if ((dirtempl == null) && ((dirtempl=loadDirectory(defname)) == null))
	    return null;
	// Clone the appropriate template:
	if ( defs == null )
	    defs = new Hashtable(7);
	if ( defs.get("directory") == null )
	    defs.put("directory", directory) ;
	if ( defs.get("identifier") == null )
	    defs.put("identifier", name) ;
	if ( defs.get("context") == null )
	    defs.put("context", getContext());
	try {
	    return (HTTPResource) dirtempl.getClone(defs);
	} catch (Exception ex) {
	    ex.printStackTrace() ;
	    return null ;
	}
    }

    /**
     * Try to create a virtual resource if the real (physical) resource
     * is not there.
     * @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.
     */

    protected HTTPResource createVirtualResource( File directory
						 , String name
						 , Hashtable defs) {
	HTTPResource dirtempl = null;
	dirtempl = loadDirectory(name);
	if (dirtempl != null) {
	    if (dirtempl.getClass().getName().equals(
		"w3c.jigsaw.resources.DirectoryResource"))
		return null; // no fake real directories
	    if (dirtempl.getClass().getName().equals(
		"w3c.jigsaw.resources.FileResource"))
		return null; 
	    if (dirtempl.getClass().getName().equals(
		"w3c.jigsaw.cvs.CvsDirectoryResource"))
		return null;
	    if ( defs == null )
		defs = new Hashtable(7);
	    if ( defs.get("directory") == null )
		defs.put("directory", directory) ;
	    if ( defs.get("identifier") == null )
		defs.put("identifier", name) ;
	    if ( defs.get("context") == null )
		defs.put("context", getContext());
	    try {
		return (HTTPResource) dirtempl.getClone(defs);
	    } catch (Exception ex) {
		ex.printStackTrace() ;
		return null ;
	    }
	}
	return null;
    }
    
    /**
     * 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(ContainerResource container
				       , w3c.jigsaw.http.Request request
				       , File directory
				       , String name
				       , Hashtable defs) {
	// Does this file exists ?
	File file = new File(directory, name) ;
	if ( ! file.exists() ) // try to create a virtual one
	    return createVirtualResource(directory, name, defs);
	// Okay, dispatch on wether it is a file or a directory.
	HTTPResource result = null;
	if ( file.isDirectory() )
	    result = createDirectoryResource(directory, name, defs) ;
	else
	    result = createFileResource(directory, name, defs) ;
	if ( result != null )
	    return result;
	// Try the super indexer if available:
	String superIndexer = getSuperIndexer();
	if ( superIndexer == null )
	    return null;
	IndexerModule m = null;
	m = (IndexerModule) getContext().getModule(IndexerModule.NAME);
	ResourceIndexer p = m.getIndexer(superIndexer);
	return ((p != null) 
		? p.createResource(container, request, directory, name, defs)
		: null);
    }


    // FIXME tests
    public SampleResourceIndexer(ResourceContext ctxt) {
	Hashtable init = new Hashtable(11);
	init.put("context", ctxt);
	init.put("identifier", "default");
	initialize(init);
    }

    public SampleResourceIndexer() {
	super();
    }
}
