// ResourceStoreManager.java
// $Id: ResourceStoreManager.java,v 1.8 1998/03/11 10:43:27 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.tools.resources.store ;

import org.w3c.tools.resources.*;
import org.w3c.tools.resources.event.*;

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

import org.w3c.util.*;

class Reference implements ResourceReference {

  public static boolean debug = false;

  // Our entry
  StoreEntry entry = null;

  // The resource identifier
  String identifier = null;

  // The default attributs
  Hashtable defs = null;

  public void updateContext(ResourceContext ctxt) {
    if (defs != null)
      defs.put("context", ctxt);
  }

  /**
   * The lock count associated to the reference.
   */
  protected int lockCount = 0;

  public int nbLock() {
    return lockCount;
  }

  protected void invalidate() {
    entry = null;
  }

  protected boolean isValid() {
    return ( entry != null ) ;
  }

  /**
   * Lock that reference in memory.
   * @return A pointer to the underlying resource, after lock succeed.
   */
  public synchronized Resource lock() 
    throws InvalidResourceException
  {
    lockCount++;
    if (entry == null)
      throw new InvalidResourceException(identifier,
					 "This reference was invalidate");
    ResourceStore store = entry.getStore();
    Resource resource = store.lookupResource(identifier);
    if (debug) {
      if (defs.get("context") == null)
	System.out.println("**** Context null for : "+identifier);
      else if (((ResourceContext)(defs.get("context"))).getServer() == null)
	System.out.println("**** Server null for "+identifier+"'s context");
    }
    if (resource == null)
      resource = store.loadResource(identifier, defs);
    if (debug)
      System.out.println("[LOCK] locking ["+lockCount+"]: "+identifier);
    return resource;
  }

  /**
   * Unlock that resource reference.
   */
  public void unlock() {
    lockCount--;
    if (debug)
      System.out.println("[LOCK] unlocking ["+lockCount+"]: "+identifier);
  }

  /**
   * Is that resource locked ?
   * @return A boolean, <strong>true</strong> if the resource is locked.
   */
  public boolean isLocked() {
    return lockCount != 0;
  }

  Reference (StoreEntry entry, String identifier, Hashtable defs) {
    this.entry      = entry;
    this.identifier = identifier;
    this.defs       = defs;
  }

}

class StoreEntry implements LRUAble, Serializable {

  boolean istransient = false;

  // our key in the cache.
  Integer key = null;

  // LRU management infos:
  transient LRUAble next = null ;
  transient LRUAble prev = null ;

  // ResourceStore infos:
  transient ResourceStore store      = null ;

  // References
  transient Hashtable references = null;

  //The manager
  transient ResourceStoreManager manager = null;

  String repository = null ;

  transient File rep = null;

  public File getRepository() {
    if (rep == null)
      rep = new File(manager.storedir, repository);
    return rep;
  }

  public LRUAble getNext() {
    return next;
  }
  
  public LRUAble getPrev() {
    return prev;
  }

  public void setNext(LRUAble next) {
    this.next = next;
  }
  
  public void setPrev(LRUAble prev) {
    this.prev = prev;
  }

  public void setTransient(boolean onoff) {
    istransient = onoff;
  }

  public boolean isTransient() {
    return istransient;
  }

  /**
   * Load the store of this entry.
   */
  synchronized ResourceStore getStore() {
    if ( store == null ) {
      store = new SimpleResourceStore() ;
      store.initialize(manager, this, getRepository()) ;
      manager.incrLoadedStore();
    }
    return store;
  }

  /**
   * Delete the store of this entry
   */
  synchronized void deleteStore() {
    Enumeration enum = references.elements();
    Reference rr = null;
    while (enum.hasMoreElements()) {
      rr = (Reference)enum.nextElement();
      rr.invalidate();
    }
    getRepository().delete();
    if (store != null) {
      store = null;
      manager.decrLoadedStore();
    }
    references = null;
  }

  /**
   * Lookup this resource.
   * @param identifier The resource identifier.
   * @return A Resource instance, or <strong>null</strong> if either the
   *    resource doesn't exist, or it isn't loaded yet.
   */
  public ResourceReference lookupResource (String identifier) {
    return (ResourceReference)references.get(identifier);
  }

  /**
   * Load a resource, or get one from the cache.
   * @param identifier The resource identifier.
   * @return A Resource instance, or <strong>null</strong> if the resource
   * doesn't exist in that storeEntry.
   * @exception InvalidResourceException If the resource couldn't be 
   * restored from its pickled format.
   */
  synchronized ResourceReference loadResource(String name, Hashtable defs) {
    ResourceReference rr = lookupResource(name);
    if (rr != null)
      return rr;
    rr = new Reference(this, name, defs);
    try {
      Resource res = rr.lock();
      if (res == null)
	return null;
    } catch (InvalidResourceException ex) {
      return null;
    } finally {
      rr.unlock();
    }
    references.put(name, rr);
    return rr;
  }

  /**
   * Save a given resource.
   * @param resource The resource to be save right now.
   */
  synchronized void saveResource(Resource resource) {
    getStore();
    store.saveResource(resource);
  }

  /**
   * Add a new resource to the resource store.
   * @param resource The resource to add.
   */
  synchronized ResourceReference addResource(Resource resource, 
					     Hashtable defs) {
    getStore();
    store.addResource(resource);
    String name = resource.getIdentifier();
    ResourceReference rr = new Reference(this, name , defs);    
    references.put(name, rr);
    return rr;
  }

  /**
   * FIXME doc
   */
  public synchronized void markModified(Resource resource) {
    getStore();
    store.markModified(resource);
  }

  /**
   * Rename a resource in this resource store.
   * @param identifier The identifier of the resource to be renamed.
   */
  public synchronized void renameResource(String oldid, String newid) {
    getStore();
    store.renameResource(oldid, newid);
    Reference rr = (Reference)lookupResource(oldid);
    if (rr != null) {
      rr.identifier = newid;
      references.remove(oldid);
      references.put(newid, rr);
    }
  }

  public synchronized Enumeration enumerateResourceIdentifiers() {
    getStore();
    return store.enumerateResourceIdentifiers();
  }

  /**
   * Remove a resource from this resource store.
   * @param identifier The identifier of the resource to be removed.
   */
  public synchronized void removeResource(String identifier) {
    getStore();
    Reference rr = (Reference)references.get(identifier);
    if (rr != null) {
      references.remove(identifier);
      manager.getEventQueue().removeSourceEvents(rr);
      rr.invalidate();
    }
    store.removeResource(identifier);
  }

  /**
   * Try unloading the space for this entry.
   */
  synchronized boolean unloadStore() {
    if ( store != null ) {
      Enumeration enum = references.elements();
      ResourceReference rr = null;
      while (enum.hasMoreElements()) {
	rr = (ResourceReference)enum.nextElement();
	if (rr.isLocked())
	  return false;
      }
      // Will the store unload itself ?
      if ( ! store.acceptUnload())
	return false;
      // Great, proceed:
      shutdownStore();
    }
    return true;
  }
  
  /**
   * Shutdown the store.
   */
  synchronized void shutdownStore() {
    if ( store != null ) {
      store.shutdown() ;
      store = null ;
      manager.decrLoadedStore();
    }
  }
  
  /**
   * Try stabilizing the store.
   */
  synchronized void saveStore() {
    if ( store != null ) {
      // Save the resource store:
      store.save();
    }
  }
  
  void initialize(ResourceStoreManager manager) {
    this.manager    = manager;
    this.references = new Hashtable();
  }

  StoreEntry(ResourceStoreManager manager, String repository, Integer key) {
    this.manager    = manager;
    this.store      = null ;
    this.repository = repository ;
    this.key        = key;
    this.references = new Hashtable();
  }

  StoreEntry(ResourceStoreManager manager, File repository, Integer key) {
    this.manager    = manager;
    this.store      = null ;
    this.rep        = repository ;
    this.key        = key;
    this.references = new Hashtable();
  }

}


class StoreManagerSweeper extends Thread {
  ResourceStoreManager manager = null ;
  boolean              killed  = false ;

  protected synchronized void waitEvent() {
    boolean done = false ;

    // Wait for an event to come by:
    while ( ! done ) {
      try {
	wait() ;
	done = true ;
      } catch (InterruptedException ex) {
      }
    }
  }

  protected synchronized void sweep() {
    notifyAll() ;
  }

  protected synchronized void shutdown() {
    killed = true ;
    notifyAll() ;
  }

  public void run() {
    while ( true ) {
      waitEvent() ;
      // The whole trick is to run the collect method of the store 
      // manager, without having the lock on the sweeper itself, so
      // that clients can still trigger it later
      if ( killed ) {
	break ;
      } else {
	try {
	  manager.collect() ;
	} catch (Exception ex) {
	  // We really don't want this thread to die
	  ex.printStackTrace() ;
	}
      }
    }
  }

  StoreManagerSweeper(ResourceStoreManager manager) {
    this.manager = manager ;
    this.setName("StoreSweeper") ;
    this.start() ;
  }

}

public class ResourceStoreManager implements ResourceSpace {

  /**
   * The identifier for resource store files.
   * All files that are used as resource store, and whose resource store 
   * implementation make use of <code>pickleProlog</code> will start
   * with a dump of that integer.
   * @see #pickleProlog
   * @see #unpickleProlog
   */
  public static final int STOREFILE_IDENTIFIER = 0x7afeefac;

  public static final String root_rep = "root.idx";

  public boolean debugMemory = true;

  /**
   * The loaded resource stores entries.
   */
  Hashtable entries = new Hashtable(); // <sentryKey - StoreEntry>
  /**
   * The max number of loaded stores
   */ 
  private int maxLoadedStore = -1;
  /**
   * The number of loaded stores
   */ 
  private int loadedStore = 0;
  /**
   * Is this store shutdown ?
   */
  protected boolean closed = false ;
  /**
   * Our store directory.
   */
  protected File storedir = null;
  /**
   * Our index file.
   */
  protected File index = null;
  /**
   * server name;
   */
  protected String server_name = null;

  /**
   * Our root repository.
   */
  protected File root_repository = null;
  /**
   * The store entries least recetenly used list.
   */
  protected LRUList lru = null;
  // FIXME doc
  protected ResourceStoreState state = null;

  /**
   * Our sweeper thread:
   */
  protected StoreManagerSweeper sweeper = null ;

  protected ResourceEventQueue eventQueue = null;

  public ResourceEventQueue getEventQueue() {
    if (eventQueue == null)
      eventQueue = new ResourceEventQueue();
    return eventQueue;
  }
  
  protected final int getMaxLoadedStore() {
    return maxLoadedStore ;
  }

  protected void incrLoadedStore() {
    loadedStore++;
    checkMaxLoadedStore();
  }

  protected void decrLoadedStore() {
    loadedStore--;
  }

  /**
   * Check that this resource store manager isn't closed.
   * @exception RuntimeException If the store manager was closed.
   */
  protected final synchronized void checkClosed() {
    if ( closed )
      throw new RuntimeException("Invalid store manager access.") ;
  }

  /**
   * Pick the least recently used entry, and remove all links to it.
   * After this method as run, the least recently used entry for some store
   * will be returned. The store manager will have discarded all its link to 
   * it, and the entry shutdown will have to be performed by the caller.
   * @return An StoreEntry instance, to be cleaned up.
   */
  protected synchronized StoreEntry pickLRUEntry() {
    return (StoreEntry) lru.removeTail();
  }

  /**
   * Collect enough entries to go back into fixed limits.
   */
  public void collect() {
    if (debugMemory)
      System.out.println("[MEMORY] start sweeper <- "+
			 loadedStore+" stores loaded.");
    StoreEntry watchdog = null;
    StoreEntry entry    = null;
    while ( loadedStore > getMaxLoadedStore() ) {
      entry = pickLRUEntry();
      if (( entry != null ) && (entry != watchdog)) {
	synchronized(entry) {
	  if (! entry.unloadStore()) {
	    lru.toHead(entry);
	    if (watchdog == null)
	      watchdog = entry;
	  }
	}
      } else {
	break;
      }
    }
    if (debugMemory)
      System.out.println("[MEMORY] sweeper done  -> "+
			 loadedStore+" stores still loaded.");
  }

  /**
   * Create a resource store repository name.
   * This method will return a new resource store repository key. When
   * used in conjunction with the <code>loadResourceStore</code> method
   * that takes a key as a parameter, this allows to caller to abstract
   * itself from the physical location of the repository.
   * @return A fresh resource store key, guaranteed to be uniq.
   */
  public String createResourceStoreRepository() {
    return "st-" + state.getNextKey();
  }
  
  public int getCurrentStoreIdentifier() {
    return state.getCurrentKey();
  }

  /**
   * Get the index file.
   * @return A File instance.
   */
  protected File getIndexFile() {
    if (index == null)
      index = new File(storedir,server_name+"-index");
    return index;
  }

  /**
   * Get the root repository.
   * @return A File instance.
   */
  protected File getRootRepository() {
    if (root_repository == null)
      root_repository = new File(storedir,root_rep);
    return root_repository;
  }

  /**
   * Get The root key.
   * @return A String instance
   */
  protected Integer getRootKey() {
    return new Integer((new String("root")).hashCode());
  }

  /**
   * Emit the given string as a warning, to whoever it is appropriate.
   * @param msg The warning message.
   */
  protected void warning(String msg) {
    System.out.println("********* WARNING *********");
    System.out.println("[" + getClass().getName()+"]:\n" + msg) ;
    System.out.println("***************************");
  }

  /**
   * Save the index file.
   */
  protected synchronized void saveEntriesIndex() {
    File   indexFile = getIndexFile();
    String dir       = indexFile.getParent();
    String name      = indexFile.getName();
    File   tmp       = new File(dir, name+".tmp");
    File   bak       = new File(dir, name+".bak");
    File   tild      = new File(dir, name+".bak~");

      ObjectOutputStream oo = null;
    try {
      oo = new ObjectOutputStream (new BufferedOutputStream
				   (new FileOutputStream
				    (tmp)));
      oo.writeObject(entries);
    } catch (IOException ex) {
      ex.printStackTrace();
      throw new RuntimeException("Unable to save entries index");
    } finally {
      if (oo != null) {
        try { oo.close(); } catch (Exception ex) {}
      }
    }

    // 1st move: delete the ~ file if any:
    if ( tild.exists() )
      tild.delete() ;
    // 2nd move rename bak to ~ (bak no longer exists)
    if ( bak.exists() ) {
      bak.renameTo(tild);
      bak.delete();
    }
    // 3nd move: rename the current index to bak
    if ( indexFile.exists() ) {
      if ( !indexFile.renameTo(bak) ) {
	warning("unable to rename "+indexFile+" to "+bak);
	tild.renameTo(bak);
      }
      indexFile.delete();
    }
    // 4th move: rename the tmp file to index
    if ( !tmp.renameTo(indexFile) ) {
      bak.renameTo(indexFile) ;
      tild.renameTo(bak);
      warning("unable to rename "+tmp+" to "+indexFile);
    }
    // cleanup (erase the ~ file)
    tild.delete() ;
  }

  /**
   * Load the index file.
   */
  protected synchronized void loadEntriesIndex() {
    File indexFile = getIndexFile();
    if (! indexFile.exists()) {
      File bak = new File(indexFile.getParent(), indexFile.getName()+".bak");
      if (bak.exists()) {
	warning(indexFile+" not found! using bak file :"+bak);
	if ( !bak.renameTo(indexFile) ) {
	  warning("unable to rename "+bak+" to "+indexFile+
		  "\n Try by yourself or all your resources will be lost!");
	  System.exit(-1);
	}
      } else {
	entries = new Hashtable();
	return;
      }
    }

    ObjectInputStream oi = null;
    try {
      oi = new ObjectInputStream(new BufferedInputStream
				 (new FileInputStream
				  (indexFile)));
      entries = (Hashtable) oi.readObject();
    } catch (ClassNotFoundException ex) {
      ex.printStackTrace();
      throw new RuntimeException("Unable to load entries index");
    } catch (IOException ex) {
      ex.printStackTrace();
      throw new RuntimeException("Unable to load entries index");
    } finally {
      if ( oi != null ) {
	try { oi.close() ; } catch (Exception ex) {}
      }
    }

    Enumeration e = entries.elements();
    while (e.hasMoreElements()) {
      StoreEntry entry = (StoreEntry)e.nextElement();
      if (entry.isTransient()) 
	entries.remove(entry.key);
      else {
	lru.toHead((LRUAble) entry);
	entry.initialize(this);
      }
    }
  }


  /**
   * Shutdown this resource store manager.
   * Go through all entries, and shut them down.
   */
  public synchronized void shutdown() {
    saveEntriesIndex();
    // Kill the sweeper thread:
    sweeper.shutdown() ;
    // Clenup all pending resource stores:
    Enumeration enum = entries.elements() ;
    while ( enum.hasMoreElements() ) {
      StoreEntry entry = (StoreEntry) enum.nextElement() ;
      entry.shutdownStore() ;
      entries.remove(entry.key) ;
    }
    closed = true ;
    // Now save our state:
    File rmstate = new File(storedir, "state");
    try {
      DataOutputStream out = (new DataOutputStream
			      (new BufferedOutputStream
			       (new FileOutputStream (rmstate))));
      state.pickle(out);
      out.close();
    } catch (Exception ex) {
      // FIXME !!
      System.out.println("ResourceStoreManager: unable to save state !");
      ex.printStackTrace();
    }
  }
  
  /**
   * Checkpoint all modified resource stores, by saving them to disk.
   */
  public void checkpoint() {
    saveEntriesIndex();
    // Checkpoint all loaded resource stores:
    Enumeration e = entries.elements();
    while ( e.hasMoreElements() ) {
      StoreEntry entry = (StoreEntry) e.nextElement();
      entry.saveStore();
    }
    // Then save our state:
    File rmstate = new File(storedir, "state");
    try {
      DataOutputStream out = (new DataOutputStream
			      (new BufferedOutputStream
			       (new FileOutputStream (rmstate))));
      state.pickle(out);
      out.close();
    } catch (Exception ex) {
      // FIXME !!
      System.out.println("ResourceStoreManager: unable to save state !");
      ex.printStackTrace();
    }
  }

  /**
   * Mark the given store as having been used recently.
   * @param token The token the resource store manager provided you when
   * it initialized the store.
   */
  public void markUsed(Object token) {
    StoreEntry entry = (StoreEntry) token;
    if ( entry != null )
      lru.toHead(entry);
  }
  
  /**
   * Dump a prolog in given stream for that resource store.
   * Using the resource store manager prolog in a resource store 
   * implementation is optional. However, it is recommended that whenever 
   * possible, you use it to get the benefits of the main method of that
   * class.
   * @param out The output stream to dump the prolog to.
   * @param store The resource store which is being dumped.
   * @exception IOException If the prolog couldn't be pickled to the
   * given output stream.
   * @see #unpickleProlog
   */
  
  public static void pickleProlog(DataOutputStream out, ResourceStore store)
    throws IOException
  {
    out.writeInt(STOREFILE_IDENTIFIER);
    out.writeUTF(store.getClass().getName());
    out.writeInt(store.getVersion());
  }

  /**
   * Read in the prolog from the given input stream.
   * This method checks the consistency between the implementation of the
   * store that wants to read from that stream, and the implementation of
   * the store that has been used to create it.
   * <p>Using that method, as well as using the <code>pickleProlog</code>
   * method is optional; however, using one makes using the other 
   * mandatory.
   * @param in The input stream to read the prolog from.
   * @param store The store that is to be created from the given input 
   * stream.
   * @exception IOException If the stream couldn't be read.
   * @exception NotAStoreException If the given repository wasn't flagged as
   * containing a resource store.
   * @exception InvalidStoreClassException If the loading class is not
   * the saving class.
   * @exception StoreClassVersionException If the saved store version number
   * doesn't match the loader's class version number.
   * @see #pickleProlog
   */
  public static void unpickleProlog(DataInputStream in,
				    ResourceStore store) 
    throws IOException,
           NotAStoreException, 
	   InvalidStoreClassException,
	   StoreClassVersionException
  {
    int id = in.readInt();
    if ( id != STOREFILE_IDENTIFIER ) 
      // We are really in a bad situation:
      throw new NotAStoreException("not a store");
    String clsname = in.readUTF();
    int    version = in.readInt();
    if ( ! store.getClass().getName().equals(clsname) ) 
      throw new InvalidStoreClassException(store.getClass().getName(),
					   clsname);
    if ( version != store.getVersion() ) 
      throw new StoreClassVersionException(store.getVersion(),
					   version);
    return;
  }

  protected boolean used(String rep) {
    Enumeration e = entries.elements();
    StoreEntry entry = null;
    while (e.hasMoreElements()) {
      entry = (StoreEntry)e.nextElement();
      System.out.println(storedir+":"+entry.repository);
      try {
	if (entry.getRepository().getName().equals(rep))
	  return true;
      } catch (Exception ex) {
	//continue
      }
    }
    return false;
  }

  public void salvage() {
    String stores[] = storedir.list();
    if ( stores != null ) {
      for (int i = 0 ; i < stores.length; i++) {
	if ((stores[i].length() <= 3) || ! stores[i].startsWith("st-"))
	  continue;
	if ( stores[i].endsWith(".bak") )
	  continue;
	//Is there a storeEntry using this repository?
	if (! used(stores[i])) {
	  // For testing now
	  System.err.println("*** "+stores[i]+" not used! deleted");
	  File notUsed = new File(storedir, stores[i]);
	  notUsed.delete();
	}
      }
    }
  }

  public void displayIndex() {
    Enumeration e = entries.elements();
    StoreEntry entry = null;
    System.out.println("Index : ");
    while (e.hasMoreElements()) {
      entry = (StoreEntry)e.nextElement();
      System.out.println(entry.key+" : "+entry.repository);
    }
  }

  /**
   * Try to salvage the resource store manager state.
   * That's pretty easy and robust, it should really work well if no
   * one hacked the store directory.
   * @return A ResourceStoreState instance.
   */
  public ResourceStoreState salvageState() {
    System.err.println("*** salvaging resource manager state...");
    File state = new File(storedir, "state");
    int  maxid = -1;
    // List all available stores in the directory:
    String stores[] = storedir.list();
    if ( stores != null ) {
      for (int i = 0 ; i < stores.length; i++) {
	if ((stores[i].length() <= 3) || ! stores[i].startsWith("st-"))
	  continue;
	if ( stores[i].endsWith(".bak") )
	  continue;
	try {
	  int id = Integer.parseInt(stores[i].substring(3));
	  maxid  = Math.max(maxid, id);
	} catch (Exception ex) {
	  ex.printStackTrace();
	}
      }
    }
    ++maxid;
    System.err.println("*** resource store state salvaged, using: "+maxid);
    return new ResourceStoreState(maxid);
  }

  /**
   * Restore the resource whose name is given from the root StoreEntry.
   * @param identifier The identifier of the resource to restore.
   * @param defs Default attribute values.
   */ 
  public ResourceReference loadRootResource(String identifier,
					    Hashtable defs)
  {
    StoreEntry entry = (StoreEntry) entries.get(getRootKey());
    if (entry == null) {
      entry = new StoreEntry(this, root_rep, getRootKey());
      entries.put(getRootKey(), entry);
    }
    return entry.loadResource(identifier, defs);
  }

  /**
   * Lookup this resource.
   * @param sentry The resource space entry.
   * @param identifier The resource identifier.
   * @return A Resource instance, or <strong>null</strong> if either the
   *    resource doesn't exist, or it isn't loaded yet.
   */
  public ResourceReference lookupResource(SpaceEntry sentry, 
					  String identifier)
  {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null)
      throw new RuntimeException("Unable to lookup resource ("+
				 identifier+
				 "), no StoreEntry for its space entry");
    return entry.lookupResource(identifier);
  }

  /**
   * Restore the resource whose name is given.
   * @param sentry The resource space entry.
   * @param identifier The identifier of the resource to restore.
   * @param defs Default attribute values.
   */
  public ResourceReference loadResource(SpaceEntry sentry, 
					String identifier,
					Hashtable defs)
  {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null) 
      throw new RuntimeException(identifier+": Space Entry not valid");
    return entry.loadResource(identifier, defs);
  }

  /**
   * Add this resource to the StoreEntry of the space entry.
   * @param sentry The resource space entry.
   * @param resource The resource to add.
   * @param defs Default attribute values.
   */
  public ResourceReference addResource(SpaceEntry sentry,
				       Resource resource,
				       Hashtable defs)
  {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null)
      throw new RuntimeException("Unable to add resource ("+
				 resource.getIdentifier()+
				 "), no StoreEntry for its space entry");
    return entry.addResource(resource, defs);
  }
  
  /**
   * Save this resource to the StoreEntry of the space entry.
   * @param sentry The resource space entry
   * @param resource The resource to save.
   */
  public void saveResource(SpaceEntry sentry,
			   Resource resource) 
  {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null)
      throw new RuntimeException("Unable to save resource ("+
				 resource.getIdentifier()+
				 "), no StoreEntry for its space entry");
    entry.saveResource(resource);
  }

  /**
   * Mark the given resource as being modified.
   * @param sentry The resource space entry.
   * @param resource The resource to mark as modified.
   */
  public void markModified(SpaceEntry sentry,
			   Resource resource) 
  {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null)
      throw new RuntimeException("Unable to mark resource ("+
				 resource.getIdentifier()+
				 "), no StoreEntry for its space entry");
    entry.markModified(resource);
  }

  /**
   * Rename a resource in this resource space.
   * @param sentry The resource space entry.
   * @param oldid The old resorce identifier.
   * @param newid The new resorce identifier.
   */
  public void renameResource(SpaceEntry sentry,
			     String oldid,
			     String newid) 
  {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null)
      throw new RuntimeException("Unable to rename resource ("+
				 oldid+" to "+newid+
				 "), no StoreEntry for its space entry");
    entry.renameResource(oldid, newid);
  }

  /**
   * delete this resource from the StoreEntry (and the repository).
   * @param sentry The resource space entry
   * @param resource The resource to delete.
   */
  public void deleteResource(SpaceEntry sentry,
			     Resource resource) 
  {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null)
      throw new RuntimeException("Unable to delete resource ("+
				 resource.getIdentifier()+
				 "), no StoreEntry for its space entry");
    entry.removeResource(resource.getIdentifier());
  }

  /**
   * Delete all the children of resource indentified by its
   * space entry.
   * @param sentry The resource space entry
   */
  public void deleteChildren(SpaceEntry sentry) {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null)
      throw new RuntimeException("Unable to delete children, no StoreEntry"+
				 " for its space entry");
    entry.deleteStore();
    entries.remove(sentry.getEntryKey());
  }

  /**
   * Save all the children of the resource indentified by its
   * spaec entry.
   * @param sentry The resource space entry
   */  
  public void saveChildren(SpaceEntry sentry) {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null)
      throw new RuntimeException("Unable to save children, no StoreEntry"+
				 " for its space entry");
    entry.saveStore();
  }

  protected void checkMaxLoadedStore() {
    // Check to see if we have exceeded our quota:
    if (loadedStore > getMaxLoadedStore()) {
      sweeper.sweep() ;
    }
  }

  /**
   * acquire children from an external file.
   * @param sentry The resource space entry.
   * @param repository The file used to store children.
   */
  public void acquireChildren(SpaceEntry sentry,
			      File repository,
			      boolean transientFlag) 
  {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null) {
      //create it.
      checkMaxLoadedStore();
      entry = new StoreEntry(this, repository, sentry.getEntryKey());
      entry.setTransient(transientFlag);
      entries.put(sentry.getEntryKey(), entry);
      // FIXME : unload index hashtable ?
    } else {
      entry.rep = repository;
    }
  }

  /**
   * Acquire the StoreEntry of the space entry.
   * @param sentry The resource space entry.
   */
  public void acquireChildren(SpaceEntry sentry) {
    // 
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null) {
      //create it.
      checkMaxLoadedStore();
      String rpath = createResourceStoreRepository();
      File repository = new File(storedir, rpath);
      if (repository.exists()) {
	String fpath = new String(rpath);
	String lpath = null;
	do {
	  lpath = new String(rpath);
	  rpath = createResourceStoreRepository();
	  repository = new File(storedir, rpath);
	} while (repository.exists());
	if (fpath.equals(lpath))
	  warning("repository "+fpath+" esists! using "+rpath);
	else
	  warning("repositories "+fpath+" to "+lpath+" exists! using "+rpath);
      }
      entry = new StoreEntry(this, rpath, sentry.getEntryKey());
      entries.put(sentry.getEntryKey(), entry);
      // FIXME : unload index hashtable ?
    }
  }

  /**
   * Enumerate the name (ie identifiers) of the space entry children.
   * @param sentry The space entry.
   * @param all Should all resources be listed.
   * @return An enumeration, providing one element per child, which is
   * the name of the child, as a String.
   */
  public Enumeration enumerateResourceIdentifiers(SpaceEntry sentry) {
    StoreEntry entry = (StoreEntry) entries.get(sentry.getEntryKey());
    if (entry == null)
      throw new RuntimeException("Unable to list children, no StoreEntry"+
				 " for its space entry");
    return entry.enumerateResourceIdentifiers();
  }

  /**
   * Create a new resource store manager for given store directory.
   * The resource store manager will manage the collection of stores 
   * contained in the directory, and keep track of the stores state.
   * @param storedir The store directory to manage.
   */
  public ResourceStoreManager (String server_name,
			       File storedir, 
			       String default_root_class,
			       String default_root_name,
			       int max_loaded_store,
			       Hashtable defs)
  {
    // Initialize the instance variables:
    this.server_name    = server_name;
    this.storedir       = storedir;
    this.entries        = new Hashtable() ;
    this.sweeper        = new StoreManagerSweeper(this) ;
    this.lru            = new AsyncLRUList();
    this.maxLoadedStore = (max_loaded_store < 10? 10 : max_loaded_store);
    this.loadedStore = 0;

    loadEntriesIndex();

    // If not already available, create the root resource, and its
    // repository.
    getRootRepository();
    if (! root_repository.exists()) {
      try {
	Class root_class = Class.forName(default_root_class);
	Resource root = (Resource) root_class.newInstance();
	if (defs == null)
	  defs = new Hashtable(4);
	defs.put("identifier", default_root_name);
	defs.put("key", getRootKey());
	root.initialize(defs);

	StoreEntry entry = new StoreEntry(this,
					  root_rep,
					  getRootKey());
	ResourceReference rr = entry.addResource(root, defs);
	ResourceContext context = (ResourceContext) defs.get("context");
	context.setResourceReference(rr);
	entry.saveResource(root);
	entries.put(getRootKey(), entry);
	saveEntriesIndex();
      } catch (InstantiationException ex) {
      } catch (IllegalAccessException ex) {
      } catch (ClassNotFoundException ex) {
	System.out.println(ex.getMessage());
	ex.printStackTrace();
      }
    }

    // If not already available, create the resource store state object:
    File rsmstate = new File(storedir, "state");
    // Restore it:
    DataInputStream in = null;
    try {
      in = (new DataInputStream
	    (new BufferedInputStream 
	     (new FileInputStream(rsmstate))));
      this.state = (ResourceStoreState) AttributeHolder.unpickle(in);
    } catch (Exception ex) {
      // Let's try to fix this:
      this.state = salvageState();
    }
    if ( in != null ) {
      try { in.close(); } catch (IOException ex) {}
    }
    //    salvage();
    //    displayIndex();
  }
  
}

