// ResourceStoreManager.java
// $Id: ResourceStoreManager.java,v 1.10 1998/07/02 07:52:07 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 = false;

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

