// NewStoreEntry.java
// $Id: NewStoreEntry.java,v 1.9 2002/08/08 12:30:15 ylafon Exp $
// (c) COPYRIGHT MIT, INRIA and Keio, 1999.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.tools.resources.store ;

import java.io.File;
import java.io.PrintStream;

import java.util.Enumeration;
import java.util.Hashtable;

import org.w3c.tools.resources.Attribute;
import org.w3c.tools.resources.AttributeHolder;
import org.w3c.tools.resources.AttributeRegistry;
import org.w3c.tools.resources.IntegerAttribute;
import org.w3c.tools.resources.InvalidResourceException;
import org.w3c.tools.resources.Resource;
import org.w3c.tools.resources.ResourceContext;
import org.w3c.tools.resources.ResourceReference;
import org.w3c.tools.resources.ServerInterface;
import org.w3c.tools.resources.StringAttribute;

import org.w3c.util.LRUAble;


class NewReference implements ResourceReference {

    public static boolean debug = false;

    // Our entry
    NewStoreEntry 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 has been invalidated");
	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;
    }

    /**
     * Lock that reference in memory.
     * @return A pointer to the underlying resource, after lock succeed.
     */
    public Resource unsafeLock() 
	throws InvalidResourceException
    {
	lockCount++;
	if (entry == null)
	    throw new InvalidResourceException(identifier,
					"This reference has been invalidated");
	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;
    }

    NewReference (NewStoreEntry entry, String identifier, Hashtable defs) {
	this.entry      = entry;
	this.identifier = identifier;
	this.defs       = defs;
    }
}

public class NewStoreEntry extends AttributeHolder implements LRUAble {

    protected static int ATTR_REPOSITORY = -1;

    protected static int ATTR_KEY = -1;

    static {
	Attribute a   = null ;
	Class     cls = null ;
	// Get a pointer to our own class:
	try {
	    cls = Class.forName(
           "org.w3c.tools.resources.store.NewStoreEntry");
	} catch (Exception ex) {
	    ex.printStackTrace() ;
	    System.exit(1) ;
	}

	// The repository attribute:
	a = new StringAttribute("repository", null, Attribute.MANDATORY);
	ATTR_REPOSITORY = AttributeRegistry.registerAttribute(cls, a);
	//The key attribute
	a = new IntegerAttribute("key", null, Attribute.MANDATORY);
	ATTR_KEY = AttributeRegistry.registerAttribute(cls, a);
    }

    protected Integer getKey() {
	return (Integer)getValue(ATTR_KEY, null);
    }

    protected File getRepository() {
	String repository = getString(ATTR_REPOSITORY, null);
	if (rep == null)
	    rep = new File(manager.storedir, repository);
	return rep;
    }

    boolean istransient = false;

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

    // ResourceStore infos:
    ResourceStore store      = null ;

    // References
    Hashtable references = null;

    //The manager
    ResourceStoreManager manager = null;

    //The repository
    File rep = null;

    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.
     */
    ResourceStore getStore() {
	if ( store == null ) {
	    synchronized (this) {
		if ( store == null ) {
		    store = new ResourceStoreImpl() ;
		    store.initialize(manager, this, getRepository(), 
				     manager.serializer) ;
		    manager.incrLoadedStore();
		}
	    }
	}
	return store;
    }

    /**
     * Delete the store of this entry
     */
    synchronized void deleteStore() {
	Enumeration enum = references.elements();
	NewReference rr = null;
	while (enum.hasMoreElements()) {
	    rr = (NewReference)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 NewReference(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 NewReference(this, name , defs);    
	references.put(name, rr);
	return rr;
    }

    /**
     * FIXME doc
     */
    public 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);
	NewReference rr = (NewReference)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();
	NewReference rr = (NewReference)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 ;
	    references = new Hashtable(3);
	    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(3);
    }

    NewStoreEntry(ResourceStoreManager manager, 
		  String repository, 
		  Integer key) 
    {
	this.manager    = manager;
	this.store      = null ;
	this.references = new Hashtable(3);
	setValue(ATTR_REPOSITORY, repository);
	setValue(ATTR_KEY, key);
    }

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

    public NewStoreEntry() {}

}


