// SampleResourceIndexer.java
// $Id: SampleResourceIndexer.java,v 1.5 1998/02/11 15:41:23 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.*;

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 = 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 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,
					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();
      // 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, 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();
  }
}
