// ResourceContainerImpl.java
// $Id: ResourceContainerImpl.java,v 1.8 1997/07/30 14:07:27 ylafon Exp $  
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.tools.resources.impl;

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

//import w3c.jigsaw.indexer.*;
import w3c.tools.resources.*;
import w3c.tools.resources.indexer.*;
import w3c.tools.resources.http.*;
import w3c.tools.resources.event.*;
import w3c.tools.resources.impl.*;

public class ResourceContainerImpl extends HTTPResource 
                                   implements ResourceContainer 
{
  /**
   * The ResourceIndexer
   */
  protected String indexerName = null;

  /**
   * The container PropertyResourceStore 
   */
  protected transient ResourceStore store = null;

  protected boolean extensibleFlag = false;

  protected File directory = null;

  /**
   * The container cache
   */
  protected transient Hashtable catalog = null;

  protected void error(String msg) {
    throw new RuntimeException(msg);
  }

  public File getDirectory() {
    return (File)holder.protectedGetValue("directory");
  }

  public void setDirectory(File directory) {
    holder.protectedSetValue("directory",directory);
  }

  protected File igetDirectory() {
    return directory;
  }

  protected void isetDirectory(File directory) {
    this.directory = directory;
  }

  public String getIndexerName() {
    return (String)holder.protectedGetValue("indexerName");
  }

  public void setIndexerName(String indexerName) {
    holder.protectedSetValue("indexerName",indexerName);
  }

  protected String igetIndexerName() {
    return indexerName;
  }

  protected void isetIndexerName(String indexerName) {
    this.indexerName = indexerName;
  }

  //
  public boolean getExtensibleFlag() {
    return ((Boolean)
	    holder.protectedGetValue("extensibleFlag")).booleanValue();
  }

  public void setExtensibleFlag(boolean extensibleFlag) {
    holder.protectedSetValue("extensibleFlag", new Boolean(extensibleFlag));
  }

  protected boolean igetExtensibleFlag() {
      return extensibleFlag;
  }

  protected void isetExtensibleFlag(boolean extensibleFlag) {
      this.extensibleFlag = extensibleFlag;
  }

  public ResourceStore getStore() {
    return store;
  }

  /**
   * Resolve the given state into a resource reference.   
   * @param state The state to be resolved.
   * @return A ResourceReference instance, or <strong>null</strong>
   */
  
  public ResourceReference resolve(LookupState state) {

    if ( ! state.hasMoreComponents() )
      return self;
    // Lookup it up:
    String name = state.peekNextComponent();
    ResourceReference rr = loadChild(name);
    if ( rr != null ) {
      state.getNextComponent();
      try {
	Resource r = rr.lock();
	return r.resolve(state);
      } finally {
	rr.unlock();
      }
    } else {
      state.getNextComponent();
      rr = getExtensibleFlag() ? createDefaultResource(name) : null ;
      Resource r = null;
      if ((rr != null) && (state.hasMoreComponents())) {
	try {
	  r = rr.lock();
	  return r.resolve(state);
	} finally {
	  rr.unlock();
	}
      }
      else  if (rr != null) {
	// add in container cache
	catalog.put(name,rr);
	return rr;
      }
      else return null;
    }
  }

  /**
   * Try creating a default resource having the given name.
   * This method will make its best effort to create a default resource
   * having this name in the directory. If a file with this name exists,
   * it will check the pre-defined admin extensions and look for a match.
   * If a directory with this name exists, and admin allows to do so, it
   * will create a sub-directory resource.
   * @param name The name of the resource to try to create.
   * @return A HTTPResource instance, if possible, <strong>null</strong>
   *    otherwise.
   */
  public synchronized ResourceReference createDefaultResource(String name) {
    // Don't automagically create resources of name '..' or '.'
    if (name.equals("..") || name.equals("."))
      return null ;
    HTTPResourceContext c = (HTTPResourceContext) getContext().getClone();
    c.setFile( new File(c.getFile(), name));
    ResourceIndexer indexer = null;
    if ( indexerName != null )
      indexer = c.getIndexer(indexerName);
    else
      indexer = c.getIndexer();
   // set default value:
    c.setContainer(this.self);
    // Create the resource, up to the call (and including it) to r.create(c);
    HTTPResource child = null;
    try {
      child = indexer.index(c);
      if ( child != null ) 
	return registerChild(child, c);
      else return null;
    } catch (ResourceInitException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
      return null;
    } catch (ChildNotSupportedException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
      return null;
    }
  }

  /**
   * Revalidate the resource, reload it from disk.
   * @param name The resource name
   * @return The resource revalidated.
   */
  public Resource revalidate(String name) {
    ResourceReference rr   = null;
    Resource result = null;
    try {
      PersistentReference pr = store.getPersistentReference(name);
      //	new PersistentReferenceImpl(store.getAbsolutePath(),name);
      rr = space.load(pr,self);
      if (rr != null) catalog.put(name,rr);
      try {
	result = rr.lock();
      } finally {
	rr.unlock();
      }
    } catch (ClassNotFoundException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
    }
    return result;
  }

  public ResourceReference loadChild(String name) {
    ResourceReference rr   = (ResourceReference) catalog.get(name);
    try {
      if (rr == null) {
	PersistentReference pr = store.getPersistentReference(name);
	rr = space.load(pr,self);
	if (rr != null) catalog.put(name,rr);
      } 
    } catch (ClassNotFoundException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
    }
    return rr;
  }

  protected void loadChildren() {
    Enumeration e = listChildNames();
    while (e.hasMoreElements())
      loadChild((String)e.nextElement());
  }

  /**
   * Do you want to unload yourself ?
   * Check wether this resource is willing to be persistified back to disk.
   * If the resource agrees to be unloaded, then it should mark itself as
   * no longer valid. The caller is than free to take any appropriate action
   * to unload that resource from memory (provided it can guarantee that no
   * one else in the system is using it).
   * @return A boolean, <strong>true</strong> if the resource can be
   * unloaded <strong>false</strong> otherwise.
   */
  public synchronized boolean unload() {
    Enumeration e = catalog.elements();
    ResourceReference rr = null;
    Resource r = null;
    // unloading my childs
    while (e.hasMoreElements()) {
      rr = (ResourceReference)e.nextElement();
      try {
	r = rr.lock();
	r.unload();
      } finally {
	rr.unlock();
      }
    }
    // unloading myself
    super.unload();
    return true;
  }

  protected void removeFromCache(String resourceName) {
    catalog.remove(resourceName);
  }

  /**
   * Register and init the given resource as a new child of the container.
   * @param resource The resource to be added as a child.
   * @param ctxt The init context to be used to init the child.
   * @exception ChildNotSupportedException If the container doesn't
   * support that kind of a child.
   * @exception ResourceInitException If the resource didn't init.
   * @exception SecurityException If that access is denied.
   */
  public ResourceReference registerChild(Resource resource, 
					 ResourceContext context) 
    throws ChildNotSupportedException, ResourceInitException
  {
    ResourceContext newContext = myContext.getClone();
    // merge parent context and given one
    String newnames[]  = context.getPropertyNames();
    Object newvalues[] = context.getPropertyValues();
    int i = 0;
    while (i < newnames.length) {
      newContext.addProperty(newnames[i],newvalues[i]);
      i++;
    }
    newContext.setContainer(self);
    resource.create(newContext); 

    String name = resource.getName();
    PersistentReference pr = store.getPersistentReference(name);
    ResourceCache cache = ((ResourceSpaceImpl)space).getCache();
    ResourceReference rr = cache.getReference(pr,resource);

    newContext.setResourceReference(rr);
    resource.init(newContext);

    space.save(resource);
    catalog.put(name,rr);
    return rr;
  }
  

  /**
   * Unregister and delete the given child resource.
   * @param child The child resource to delete.
   * @exception SecurityException If that access is denied.
   */
  public void deleteChild(Resource resource) {
    catalog.remove(resource.getName());
    space.delete(resource);
  }
  
  /**
   * Unregister and delete the child (if any) that has the given name.
   * @param name The name of the child resource to delete.
   * @exception SecurityException If that access is denied.
   */
  public void deleteChild(String name) {
    ResourceReference rr = (ResourceReference) catalog.get(name);
    if (rr == null) space.delete(name,self);
    else {
      Resource r = null;
      try {
	r = rr.lock();
	// rr.invalidate(); //SURE?
	deleteChild(r);
      } finally {
	rr.unlock();
      }
    }
  }

  /**
   * Enumerate all child resource names.
   * @return An enumeration of String instances, providing each
   * child resource names
   * @exception SecurityException If that access is denied.
   */
  public Enumeration listChildNames() {
    return store.listResourceName();
  }

  /**
   * Enumerate all child resources.
   * @return An enumeration of ResourceReference instances.
   * @exception SecurityException If that access is denied.
   */
  public Enumeration listChildren() {
    loadChildren();
    return catalog.elements();
  }
  
  class ProtectedInvoker implements Runner {

    public Object run(java.lang.reflect.Method m, Object args[]) 
      throws IllegalAccessException
    , java.lang.reflect.InvocationTargetException
    {
      return m.invoke(ResourceContainerImpl.this, args);
    }
    
  }

  /**
   * Initialize that resource within the given context.
   * A resource implementor is <strong>guaranteed</strong> that this method
   * will get called after the instance carries its proper attribute
   * settings and before any other access to the resource is performed.
   * @param context The context in which that resource is to initialize
   * itself.
   * @exception ResourceInitException If the resource is not able to 
   * initialize itself in the given context.
   * @exception SecurityException If that access is denied.
   */
  public void init(ResourceContext context)
    throws ResourceInitException
  {
    this.myContext = context;
    this.self      = myContext.getResourceReference();
    this.space     = myContext.getResourceSpace();
    this.container = myContext.getContainer();
    this.delegator = new ResourceDelegatorImpl(self);
//     if (!(this instanceof DirectoryResource) && (directory != null)) {
//       System.out.println("$$$$$$$$$$$$$$$$$$$$$");
//       if (! directory.exists()) {
// 	directory.mkdirs();
// 	myContext.addProperty("directory",directory);
//       }
//       else  if (! directory.isDirectory())
// 	throw new ResourceInitException(directory.getAbsolutePath()+
// 					" exists and is not a directory.");
//     }
    // Simple Resources don't call create
    if (holder == null)
      holder = space.getPropertyHolder( new ProtectedInvoker(), this);
    if (catalog == null)
      catalog = new Hashtable(20);

    ((HTTPResourceContext)context).setPath(getName());
    setUrlPath(((HTTPResourceContext)context).getPath());
    // the root container has no container 
    if (container != null) {
      ResourceContainerImpl c = (ResourceContainerImpl)container.lock();
      store = (c.store).addStore(getName());
    }
    space.resourceInited(this);
  }


  public ResourceContainerImpl(ResourceSpace space, 
			       ResourceStore store)
  {
    this.store     = store;
    this.space     = space;
    this.catalog   = new Hashtable(20);
  }
   

  public ResourceContainerImpl() {
    super();
    this.catalog   = new Hashtable(20);
  }

  static final long serialVersionUID = -4404991346344457882L;

}
