    // ContainerResource.java
    // $Id: ContainerResource.html,v 1.2 1999/10/27 22:10:33 ylafon Exp $
    // (c) COPYRIGHT MIT and INRIA, 1996.
    // Please first read the full copyright statement in file COPYRIGHT.html

    package org.w3c.tools.resources ;

    import java.util.*;

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

    /**
     * This resource manage children resources.
     */
    public class ContainerResource extends AbstractContainer {

        /**
         * Attribute index - The index of the resource key.
         */
        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.ContainerResource") ;
    	} catch (Exception ex) {
    	    ex.printStackTrace() ;
    	    System.exit(1) ;
    	}
    	// The identifier attribute:
    	a = new IntegerAttribute("key",
    				 null,
    				 Attribute.COMPUTED);
    	ATTR_KEY = AttributeRegistry.registerAttribute(cls, a);
        }

        public Object getClone(Object values[]) {
    	values[ATTR_KEY] = null;
    	return super.getClone(values);
        }

        /**
         * Get the container Key. This key must be unique and unchanged
         * during the container life.
         * @return a String instance.
         */
        public Integer getKey() {
    	Integer key = (Integer) getValue(ATTR_KEY, null);
    	if (key == null) {
    	    key = new Integer(getIdentifier().hashCode() ^
    			      (new Date().hashCode()));
    	    setValue(ATTR_KEY, key);
    	}
    	return key;
        }

        protected SpaceEntry getSpaceEntry() {
    	ResourceReference rr = getParent();
    	if (rr == null) //I'm root or external!!
    	    return new SpaceEntryImpl(this);
    	try {
    	    //FIXME sure that is a containerResource?
    	    ContainerResource cont = (ContainerResource) rr.lock();
    	    return new SpaceEntryImpl(cont);
    	} catch (InvalidResourceException ex) {
    	    System.out.println(ex.getMessage());
    	    ex.printStackTrace();
    	    return null;
    	} finally {
    	    rr.unlock();
    	}
        }

        /**
         * Get the SpaceEntry of our children resources.
         * @return A SpaceEntry instance.
         */
        protected SpaceEntry getChildrenSpaceEntry() {
    	return new SpaceEntryImpl( this );
        }

        /**
         * This handles the <code>RESOURCE_MODIFIED</code> kind of events.
         * @param evt The StructureChangeEvent.
         */

        //FIXME EVENT, we don't know if this resource is valid.
        public void resourceModified(StructureChangedEvent evt) {
    	super.resourceModified(evt);
        }

        /**
         * A new resource has been created in some space.
         * This handles the <code>RESOURCE_CREATED</code> kind of events.
         * @param evt The event describing the change.
         */

        public void resourceCreated(StructureChangedEvent evt) {
    	super.resourceCreated(evt);
        }

        /**
         * A resource is about to be removed
         * This handles the <code>RESOURCE_REMOVED</code> kind of events.
         * @param evt The event describing the change.
         */

        public void resourceRemoved(StructureChangedEvent evt) {
    	super.resourceRemoved(evt);
        }

        /**
         * Update default child attributes.
         * A parent can often pass default attribute values to its children,
         * such as a pointer to itself (the <em>parent</em> attribute).
         * <p>This is the method to overide when you want your container
         * to provide these kinds of attributes. By default this method will set
         * the following attributes:
         * <dl><dt>name<dd>The name of the child (it's identifier) -
         * String instance.
         * <dt>parent<dd>The parent of the child (ie ourself here) -
         * a ContainerResource instance.
         * <dt>url<dd>If a <em>identifier</em> attribute is defined, that
         * attribute is set to the full URL path of the children.
         * </dl>
         */

        protected ResourceContext updateDefaultChildAttributes(Hashtable attrs) {
    	ResourceContext context = super.updateDefaultChildAttributes(attrs);
    	if (context == null) {
    	    context = new ResourceContext(getContext());
    	    attrs.put("context", context) ;
    	}
    	String name = (String) attrs.get("identifier");
    	if ( name != null )
    	    attrs.put("url", getURLPath()+name);
    	return context;
        }

        /**
         * Enumerate children resource identifiers.
         * @param all Should all resources be enumerated ? Resources are often
         * created on demand only, this flag allows the caller to tell the
         * container about wether it is interested only in already created
         * resources, or in all resources (even the one that have not yet been
         * created).
         * @return An String enumeration, one element per child.
         */

        public synchronized Enumeration enumerateResourceIdentifiers(boolean all) {
    	ResourceSpace space = getSpace();
    	acquireChildren();
    	return space.enumerateResourceIdentifiers( getChildrenSpaceEntry() );
        }

        /**
         * Create a default child resource in that container.
         * This method is called by the editor to add a default resource
         * in the container under the given name. The meaning of <em>default</em>
         * is left up to the container here.
         * @param name The identifier for the new resource.
         */
        public  ResourceReference createDefaultResource(String name) {
    	return null;
        }

        /**
         * Lookup a children in the container.
         * @param name The name of the children to lookup.
         */

        public ResourceReference lookup(String name) {
    	acquireChildren();
    	SpaceEntry sp = getChildrenSpaceEntry();
    	ResourceSpace space = getSpace();
    	ResourceReference rr = space.lookupResource(sp, name);
    	if (rr != null)
    	    return rr;
    	Hashtable defs = new Hashtable(5) ;
    	defs.put("identifier", name);
    	ResourceContext context = updateDefaultChildAttributes(defs);

    	rr = space.loadResource(sp, name, defs);
    	if (rr != null) {
    	    context.setResourceReference(rr);
    	    try {
    		Resource resource = rr.lock();
    		if (resource instanceof FramedResource) {
    		    FramedResource fres = (FramedResource) resource;
    		    fres.addStructureChangedListener(this);
    		    // send event
    		}
    	    } catch (InvalidResourceException ex) {
    		// nothing here
    	    } finally {
    		rr.unlock();
    	    }
    	}
    	return rr;
        }

        /**
         * Lookup the next component of this lookup state in here.
         * @param ls The current lookup state.
         * @param lr The lookup result under construction.
         * @exception ProtocolException If an error occurs.
         * @return A boolean, <strong>true</strong> if lookup has completed,
         * <strong>false</strong> if it should be continued by the caller.
         */
        public boolean lookup(LookupState ls, LookupResult lr)
    	throws ProtocolException
        {
    	// Give a chance to our super-class to run its own lookup scheme:
    	if ( super.lookup(ls, lr) )
    	    return true;
    	// Perform our own lookup on the next component:
    	String name = ls.getNextComponent() ;
    	ResourceReference rr = null;
    	rr = lookup(name);
    	if (rr == null) {
    	    lr.setTarget(null);
    	    return false;
    	}
    	try {
    	    lr.setTarget(rr);
    	    FramedResource resource = (FramedResource) rr.lock();
    	    return (resource != null ) ? resource.lookup(ls, lr) : false;
    	} catch (InvalidResourceException ex) {
    	    return false;
    	} finally {
    	    rr.unlock();
    	}
        }

        /**
         * Remove a child resource from that container.
         * @param name The name of the child to remove.
         * @exception MultipleLockException If somone else has locked the
         * resource.
         */

        public void delete(String name)
    	throws MultipleLockException
        {
    	ResourceReference rr = null;
    	rr = lookup(name);

    	if (rr != null) {
    	    try {
    		synchronized (rr) {
    		    Resource resource = rr.lock();
    		    if (resource instanceof FramedResource)
    			((FramedResource)resource).
    			    removeStructureChangedListener(this);
    		    resource.delete();
    		}
    	    } catch (InvalidResourceException ex) {
    		// FIXME ??
    	    } finally {
    		rr.unlock();
    	    }
    	}
        }

        /**
         * Delete that container and its children if children is true
         * @exception MultipleLockException If somone else has locked one
         * of the resource child.
         */
        public synchronized void replace(DirectoryResource newdir)
    	throws MultipleLockException
        {
    	Enumeration       e        = enumerateResourceIdentifiers();
    	ResourceReference rr       = null;
    	Resource          resource = null;
    	while (e.hasMoreElements()) {
    	    rr = lookup((String) e.nextElement());
    	    if (rr != null) {
    		try {
    		    resource = rr.lock();
    		    ResourceContext ctxt = new ResourceContext(
    			newdir.getContext());
    		    resource.setContext(ctxt, true);
    		    if (resource instanceof FramedResource) {
    			((FramedResource)resource).
    			    removeStructureChangedListener(this);
    			((FramedResource)resource).
    			    addStructureChangedListener(newdir);
    		    }
    		} catch (InvalidResourceException ex) {
    		    // do nothing , continue
    		} finally {
    		    rr.unlock();
    		}
    	    }
    	}
    	super.delete();
        }

        /**
         * Delete that resource container.
         * @exception MultipleLockException If somone else has locked the
         * resource.
         */
        public synchronized void delete()
    	throws MultipleLockException
        {
    	disableEvent();
    	ResourceSpace space = getSpace();
    	//delete our children
    	acquireChildren();
    	// check for lock on children
    	Enumeration       e        = enumerateResourceIdentifiers();
    	ResourceReference rr       = null;
    	Resource          resource = null;

    	while (e.hasMoreElements())
    	    delete((String)e.nextElement());

    	SpaceEntry sentry = getChildrenSpaceEntry();
    	//delete myself
    	super.delete();
    	space.deleteChildren(sentry);
        }

        /**
         * This resource is being unloaded.
         * The resource is being unloaded from memory, perform any additional
         * cleanup required.
         */
        public void notifyUnload() {
    	super.notifyUnload();
    	// anything else?
        }

        protected boolean acquired = false;

        /**
         * Acquire our children. Must be called before all child manipulation.
         */
        protected synchronized void acquireChildren() {
    	if (!acquired) {
    	    ResourceSpace space = getSpace();
    	    space.acquireChildren( getChildrenSpaceEntry() );
    	    acquired = true;
    	}
        }

        /**
         * Add an initialized resource into this store container instance.
         * @param resource The resource to be added to the store.
         */

        protected synchronized ResourceReference addResource(Resource resource,
    							 Hashtable defs) {
    	acquireChildren();
    	ResourceReference rr = getSpace().addResource(getChildrenSpaceEntry() ,
    						      resource,
    						      defs);
    	resource.getContext().setResourceReference(rr);
    	if (resource instanceof FramedResource) {
    	    FramedResource fres = (FramedResource) resource;
    	    fres.addStructureChangedListener(this);
    	}
    	markModified() ;
    	postStructureChangedEvent(rr, Events.RESOURCE_CREATED);
    	return rr;
        }

        /**
         * Initialize and register the given resource within that container.
         * @param name The identifier for the resource.
         * @param resource An unitialized resource instance.
         * @param defs A default set of init attribute values (may be
         * <strong>null</strong>).
         * @exception InvalidResourceException If an error occurs during
         * the registration.
         */

        public void registerResource(String name,
    				 Resource resource,
    				 Hashtable defs)
    	throws InvalidResourceException
        {
    	acquireChildren();
    	// Create a default set of attributes:
    	if ( defs == null )
    	    defs = new Hashtable(11) ;
    	defs.put("identifier", name);
    	ResourceContext context =  updateDefaultChildAttributes(defs);
    	if (context != null) {
    	    resource.initialize(defs);
    	    ResourceReference rr;
    	    rr = getSpace().addResource(getChildrenSpaceEntry(),
    					resource,
    					defs);
    	    context.setResourceReference(rr);
    	    if (resource instanceof FramedResource) {
    		FramedResource fres = (FramedResource) resource;
    		fres.addStructureChangedListener(this);
    		// send event
    	    }
    	    markModified();
    	    postStructureChangedEvent(rr, Events.RESOURCE_CREATED);
    	} else {
    	    throw new InvalidResourceException(getIdentifier(),
    					       name,
    					       "unable to get context");
    	}
        }

        /**
         * Initialize ourself.
         * As we are a container resource that really contains something, we make
         * sure our URL ends properly with a slash.
         * @param values Our default attribute values.
         */
        public void initialize(Object values[]) {
    	super.initialize(values);
    	disableEvent();
    	// If my URL doesn't end with a slah, correct it:
    	String url = getURLPath() ;
    	if ((url != null) && ! url.endsWith("/") )
    	    setValue(ATTR_URL, url+"/") ;
    	acquired = false;
    	enableEvent();
        }
    }