// SampleResourceIndexer.java
// $Id: SampleResourceIndexer.java,v 1.6 1998/06/11 12:40:45 bmahe Exp $  
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.tools.resources.indexer;

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

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

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
{
    protected static final boolean extCaseSensitive = false;
    protected 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 = 
	    org.w3c.tools.resources.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"
				, "org.w3c.tools.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 ResourceReference directories = null;
    protected ResourceReference extensions  = null;
    protected ResourceReference contentTypes  = null;

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

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

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

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

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

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

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

    public synchronized void delete() 
	throws MultipleLockException
    {
    // Remove the two stores we are responsible for:
    DummyResourceReference rr = (DummyResourceReference) getExtensions();
    try {
	Resource r = rr.lock();
	r.delete();
    } catch (InvalidResourceException ex) {
    } finally {
	rr.invalidate();
	rr.unlock();
    }

    rr = (DummyResourceReference) getDirectories();
    try {
	Resource r = rr.lock();
	r.delete();
    } catch (InvalidResourceException ex) {
    } finally {
	rr.invalidate();
	rr.unlock();
    }

    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 ResourceReference loadExtension (String name) {
    // Bootstraping only, should be left commented.
    String key = extCaseSensitive ? name : name.toLowerCase();
    ResourceReference rr = getExtensions();
    try {
	TemplateContainer exts = (TemplateContainer) rr.lock();
	return exts.lookup(key);
    } catch (InvalidResourceException ex) {
	String msg = ("[resource indexer]: extensions \""+
		      name+
		      "\" couldn't be restored ("+ex.getMessage()+")");
	getContext().getServer().errlog(msg);
	return null;
    } finally {
	rr.unlock();
    }
}

/**
 * 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 ResourceReference getTemplateFor(String ext) {
    ResourceReference rr = loadExtension(ext) ;
    if (rr != null) {
	try {
	    Resource template = rr.lock();
	    if (template != null) {
		FramedResource check = new FramedResource();
		if (template.getClass() == check.getClass())
		    return null;
		else return rr;
	    }
	    return null;
	} catch (InvalidResourceException ex) {
	    return null;
	} finally {
	    rr.unlock();
	}
    } 
    return null;
}

/**
 * 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(Resource template,
					   String ext,
					   Hashtable into) {
    Attribute    attrs[] = template.getAttributes();
    ResourceReference rr = loadExtension(ext) ;
    if (rr != null) {
	try {
	    Resource e       = rr.lock() ;
	    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 ;
	} catch (InvalidResourceException ex) {
	    return null;
	} finally {
	    rr.unlock();
	}
    }
    return null;
}

     
/**
 * 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 Resource, or <strong>null</strong> if
 *    we were unable to create it.
 */

protected Resource createFileResource(File directory,
				      RequestInterface req,
				      String name,
				      Hashtable defs) 
    {
	File         file     = new File(directory, name) ;
	ResourceReference rr = null;
	FramedResource 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--) {
	    rr = getTemplateFor(exts[i]) ;
	    if ( rr != null )
		break ;
	}
	if ( rr == null ) {
	    // Look for a default template:
	    if ((rr = 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());
	try {
	    template = (FramedResource) rr.lock();
	    if (exts != null) {
		// 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 (FramedResource) template.getClone(defs);
	    } catch (Exception ex) {
		ex.printStackTrace() ;
		return null ;
	    }
	} catch (InvalidResourceException ex) {
	    ex.printStackTrace();
	    return null;
	} finally {
	    rr.unlock();
	}
    }

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

public synchronized ResourceReference loadDirectory(String name) {
    ResourceReference rr = getDirectories();
    try {
	TemplateContainer dirs = (TemplateContainer) rr.lock();
	return dirs.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;
    } finally {
	rr.unlock();
    }
}
    
/**
 * 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 A Resource  instance, or <strong>null</strong> if
 *    the indexer was unable to build a default resource for the directory.
 */

protected Resource createDirectoryResource(File directory,
					   String name,
					   Hashtable defs) 
{
    // Lookup the directory path, for an existing template.
    File         dir      = new File(directory, name) ;
    Resource dirtempl = null;
    ResourceReference rr = null;

    rr = loadDirectory(name);
    // If no template available, default to a raw DirectoryResource
    if ((rr == null) && ((rr=loadDirectory(defname)) == null))
	return null;
    try {
	dirtempl = rr.lock();
	// 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) ;
	//FIXME context ???
	//      if ( defs.get("context") == null )
	//	defs.put("context", getContext());
	try {
	    return (Resource) dirtempl.getClone(defs);
	} catch (Exception ex) {
	    ex.printStackTrace() ;
	    return null ;
	}
    } catch (InvalidResourceException ex) {
	ex.printStackTrace();
	return null;
    } finally {
	rr.unlock();
    }
}

/**
 * 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 Resource createVirtualResource( File directory,
					  String name,
					  Hashtable defs) 
{
    ResourceReference rr = null;
    Resource dirtempl = null;

    rr = loadDirectory(name);
    if (rr != null) {
	try {
	    dirtempl = rr.lock();
	    if (dirtempl.getClass().getName().equals(
				     "org.w3c.tools.resources.FileResource"))
		return null; // no fake real directories
	    if (dirtempl.getClass().getName().equals(
			     "org.w3c.tools.resources.DirectoryResource"))
		return null; // no fake real directories
	    if (dirtempl.getClass().getName().equals(
			     "org.w3c.jigsaw.resources.DirectoryResource"))
		return null; // no fake real directories
	    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 (Resource) dirtempl.getClone(defs);
	    } catch (Exception ex) {
		ex.printStackTrace() ;
		return null ;
	    }
	} catch (InvalidResourceException ex) {
	    ex.printStackTrace();
	    return null;
	} finally {
	    rr.unlock();
	}
    }
    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 Resource createResource(ContainerResource container,
			       RequestInterface 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.
    Resource result = null;
    if ( file.isDirectory() )
	result = createDirectoryResource(directory, name, defs) ;
    else
	result = createFileResource(directory, request, 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);
    ResourceReference rri = m.getIndexer(superIndexer);
    if (rri == null)
	return null;
    try {
	ResourceIndexer p = (ResourceIndexer)rri.lock();
	return ((p != null) 
		? p.createResource(container, request, directory, name, defs)
		: null);
    } catch (InvalidResourceException ex) {
	return null;
    } finally {
	rri.unlock();
    }
}


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

public SampleResourceIndexer() {
    super();
}
}
