    // FramedResource.java
    // $Id: FramedResource.html,v 1.1 1998/07/09 10:49:23 benoit 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 java.io.* ;

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

    /**
     * A FramedResource manage frames which are called during the
     * lookup and the perform.
     */
    public class FramedResource extends Resource
                                implements FrameEventListener
    {

        /**
         * The ResourceReference of frames.
         */
        class FrameReference implements ResourceReference {

    	Class             frameClass = null;
    	ResourceReference framedr    = null;

    	int lockCount = 0;

    	public void updateContext(ResourceContext ctxt) {
    	    //nothing to do
    	}

    	public int nbLock() {
    	    return lockCount;
    	}

    	/**
    	 * Lock the refered resource in memory.
    	 * @return A real pointer to the resource.
    	 */

    	public Resource lock()
    	    throws InvalidResourceException
    	{
    	    FramedResource res = (FramedResource)framedr.lock();
    	    lockCount++;
    	    return res.getFrame(frameClass);
    	}

    	/**
    	 * Unlock that resource from memory.
    	 */

    	public void unlock() {
    	    framedr.unlock();
    	    lockCount--;
    	}

    	/**
    	 * Is that resource reference locked ?
    	 */

    	public boolean isLocked() {
    	    return lockCount != 0;
    	}

    	FrameReference (ResourceFrame rframe, ResourceReference framedr) {
    	    this.frameClass = rframe.getClass();
    	    this.framedr = framedr;
    	}
        }

        /**
         * Our frames references.
         */
        protected transient Hashtable framesRef = null;
        //<ResourceFrame, Reference>

        /**
         * Our AttributeChangedListener.
         */
        protected transient AttributeChangedListener attrListener = null;

        /**
         * Our StructureChangedListener.
         */
        protected transient StructureChangedListener structListener = null;

        protected transient boolean debugEvent = false;

        protected transient boolean event_disabled = false;

        protected void disableEvent() {
    	event_disabled = true;
        }

        protected void enableEvent() {
    	event_disabled = false;
        }

        /**
         * Attribute index - The object identifier.
         */
        protected static int ATTR_OID = -1;

        static {
    	Attribute a   = null ;
    	Class     cls = null ;
    	// Get a pointer to our class:
    	try {
    	    cls = Class.forName("org.w3c.tools.resources.FramedResource") ;
    	} catch (Exception ex) {
    	    ex.printStackTrace() ;
    	    System.exit(1) ;
    	}
    	// The object identifier, *should* be uniq (see below)
    	a = new IntegerAttribute("oid",
    				 null,
    				 Attribute.COMPUTED);
    	ATTR_OID = AttributeRegistry.registerAttribute(cls, a);
        }

        public Object getClone(Object values[]) {
    	FramedResource clone   = (FramedResource) super.getClone(values);
    	clone.framesRef      = new Hashtable(3);
    	return clone;
        }

        /**
         * Get the server this resource is served by.
         * @return The first instance of Jigsaw this resource was attached to.
         */
        public ServerInterface getServer() {
    	return ((ResourceContext) getValue(ATTR_CONTEXT, null)).getServer();
        }

        /**
         * Get this resource's object identifier.
         * An object identifier is to be used specifically in etags. It's purpose
         * is to uniquify the etag of a resource. It's computed as a random number
         *, on demand only.
         * @return A uniq object identifier for that resource, as an inteeger.
         */
        public int getOid() {
    	int oid = getInt(ATTR_OID, -1);
    	if ( oid == -1 ) {
    	    double d = Math.random() * ((double) Integer.MAX_VALUE);
    	    setInt(ATTR_OID, oid = (int) d);
    	}
    	return oid;
        }

        protected void displayEvent(FramedResource fr, EventObject evt) {
    	if (debugEvent) {
    	    System.out.println(">>> ["+fr.getIdentifier()+
    			       "] has receive "+evt);
    	}
        }

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

        public void frameAdded(FrameEvent evt) {
    	displayEvent( this, evt );
    	markModified();
        }

        /**
         * This handles the <code>FRAME_MODIFIED</code> kind of events.
         * @param evt The event describing the change.
         */

        public void frameModified(FrameEvent evt) {
    	displayEvent( this, evt );
    	markModified();
        }

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

        public void frameRemoved(FrameEvent evt) {
    	displayEvent( this, evt );
    	markModified();
        }

        /**
         * Initialize and attach a new ResourceFrame to that resource.
         * @param frame An uninitialized ResourceFrame instance.
         * @param defs A default set of attribute values.
         */

        public void registerFrame(ResourceFrame frame, Hashtable defs) {
    	super.registerFrame(frame,defs);
    	frame.addFrameEventListener(this);
    	addAttributeChangedListener(frame);
    	frame.registerResource(this);
        }

        /**
         * Register a new ResourceFrame if none (from the same class) has been
         * registered.
         * @param classname The ResourceFrame class
         * @param identifier The ResourceFrame identifier
         * @exception ClassNotFoundException if the class can't be found
         * @exception IllegalAccessException if the class or initializer is not
         * accessible
         * @exception InstantiationException if the class can't be instanciated
         * @exception ClassCastException if the class is not a ResourceFrame
         */
        protected void registerFrameIfNone(String classname, String identifier)
    	throws ClassNotFoundException,
    	       IllegalAccessException,
    	       InstantiationException,
    	       ClassCastException
        {
    	Class frameclass =
    	    Class.forName(classname);
    	ResourceFrame frame = getFrame(frameclass);
    	if (frame == null) {
    	    Hashtable defs = new Hashtable(3);
    	    defs.put("identifier" , identifier);
    	    registerFrame( (ResourceFrame)frameclass.newInstance() , defs );
    	}
        }

        /**
         * Unregister a resource frame from the given resource.
         * @param frame The frame to unregister from the resource.
         */

        public synchronized void unregisterFrame(ResourceFrame frame) {
    	super.unregisterFrame(frame);
    	frame.unregisterResource(this);
    	frame.removeFrameEventListener(this);
    	removeAttributeChangedListener(frame);
        }

        private ResourceReference[] getReferenceArray(ResourceFrame[] frames) {
    	if (frames == null)
    	    return null;
    	ResourceReference[] refs = new ResourceReference[frames.length];
    	ResourceReference rr = null;
    	for (int i=0 ; i < frames.length ; i++) {
    	    rr = (ResourceReference)framesRef.get(frames[i]);
    	    if (rr == null) {
    		rr = (ResourceReference)
    		    new FrameReference(frames[i],
    				       getResourceReference());
    		framesRef.put(frames[i],rr);
    	    }
    	    refs[i] = rr;
    	}
    	return refs;
        }

        /**
         * Collect all frames references.
         * @return An array of ResourceReference, containing a set of
         * FrameReference instances or <strong>null</strong> if no resource
         * frame is available.
         */
        public synchronized ResourceReference[] getFramesReference() {
    	return getReferenceArray(getFrames());
        }

        /**
         * Collect any frame reference pointing to an instance of the given class.
         * @param cls The class of frames we are looking for.
         * @return An array of ResourceReference, containing a set of
         * FrameReference pointing to instances of the given class, or
         * <strong>null</strong> if no resource frame is available.
         */
        public synchronized ResourceReference[] collectFramesReference(Class c) {
    	return getReferenceArray(collectFrames(c));
        }

        /**
         * Get the first occurence of a frame of the given class.
         * @param cls The class of te frame to look for.
         * @return A ResourceReference instance, or <strong>null</strong>.
         */
        public synchronized ResourceReference getFrameReference(Class c) {
    	ResourceFrame     frame = getFrame(c);
    	if (frame == null)
    	    return null;
    	ResourceReference  rr = (ResourceReference)framesRef.get(frame);
    	if (rr == null) {
    	    rr = (ResourceReference)
    		new FrameReference(frame,
    				   getResourceReference());
    	    framesRef.put(frame,rr);
    	}
    	return rr;
        }

        /**
         * Get The FrameReference of the given frame, or <strong>null</strong>
         * if the frame is not registered.
         * @param frame The ResourceFrame.
         * @return A ResourceReference instance.
         */
        public synchronized
    	ResourceReference getFrameReference(ResourceFrame frame) {
    	ResourceReference rr = (ResourceReference)framesRef.get(frame);
    	if (rr == null) {
    	    rr = (ResourceReference)
    		new FrameReference(frame,
    				   getResourceReference());
    	    framesRef.put(frame,rr);
    	}
    	return rr;
        }

        /**
         * (AWT Like), dspatch the Event to all our listeners.
         * @param evt The resourceEvent to dispatch.
         */
        public void processEvent(ResourceEvent evt) {
    	if (evt instanceof StructureChangedEvent) {
    	    fireStructureChangedEvent((StructureChangedEvent)evt);
    	} else if (evt instanceof AttributeChangedEvent) {
    	    fireAttributeChangeEvent((AttributeChangedEvent)evt);
    	}
        }

        /**
         * Post an Event in the Event Queue.
         * @param evt The Event to post.
         */
        public void postEvent(ResourceEvent evt) {
    	if (event_disabled)
    	    return;
    	ResourceSpace space = getSpace();
    	if (space != null)
    	    space.getEventQueue().sendEvent(evt);
        }

        /**
         * Add an attribute change listener.
         * @param l The new attribute change listener.
         */

        public void addAttributeChangedListener(AttributeChangedListener l) {
    	attrListener = ResourceEventMulticaster.add(attrListener, l);
        }

        /**
         * Remove an attribute change listener.
         * @param l The listener to remove.
         */

        public void removeAttributeChangedListener(AttributeChangedListener l) {
    	attrListener = ResourceEventMulticaster.remove(attrListener, l);
        }

        /**
         * post an attribute change event. Actually this kind of event should
         * not be posted. So fire them!
         * @param idx The index of the attribute that has changed.
         * @param newvalue The new value for that attribute.
         */

        protected void postAttributeChangeEvent(int idx, Object newvalue) {
    	if (( attrListener != null ) && (getResourceReference() != null)) {
    	    AttributeChangedEvent evt =
    		new AttributeChangedEvent(getResourceReference(),
    					  attributes[idx],
    					  newvalue);
    	    fireAttributeChangeEvent(evt);
    	}
        }

        /**
         * Fire an attribute change event.
         * @param evt the AttributeChangedEvent to fire.
         */
        protected void fireAttributeChangeEvent(AttributeChangedEvent evt) {
    	if ( attrListener != null )
    	    attrListener.attributeChanged(evt);
        }

        /**
         * Add a structure change listener.
         * @param l The new structure change listener.
         */

        public void addStructureChangedListener(StructureChangedListener l) {
    	structListener = ResourceEventMulticaster.add(structListener, l);
        }

        /**
         * Remove a structure change listener.
         * @param l The listener to remove.
         */

        public void removeStructureChangedListener(StructureChangedListener l) {
    	structListener = ResourceEventMulticaster.remove(structListener, l);
        }

        /**
         * post an structure change event.
         * @param rr the ResourceReference of the source.
         * @param type The type of the event.
         */
        protected void postStructureChangedEvent(ResourceReference rr, int type) {
    	if ((structListener != null) && (rr != null)) {
    	    StructureChangedEvent evt =
    		new StructureChangedEvent(rr, type);
    	    postEvent(evt);
    	}
        }

        /**
         * post an structure change event.
         * @param type The type of the event.
         */
        protected void postStructureChangedEvent(int type) {
    	if ((structListener != null) && (getResourceReference() != null)) {
    	    StructureChangedEvent evt =
    		new StructureChangedEvent(getResourceReference(), type);
    	    postEvent(evt);
    	}
        }

        /**
         * Fire an structure change event.
         * @param type The type of the event.
         */
        protected void fireStructureChangedEvent(int type) {
    	if ((structListener != null) && (getResourceReference() != null)) {
    	    StructureChangedEvent evt =
    		new StructureChangedEvent(getResourceReference(), type);
    	    fireStructureChangedEvent(evt);
    	}
        }

        /**
         * Fire an structure change event.
         * @param evt the StructureChangedEvent to fire.
         */
        protected void fireStructureChangedEvent(StructureChangedEvent evt) {
    	if (structListener != null) {
    	    int type = evt.getID();
    	    switch (type) {
    	    case Events.RESOURCE_MODIFIED :
    		structListener.resourceModified(evt);
    		break;
    	    case Events.RESOURCE_CREATED :
    		structListener.resourceCreated(evt);
    		break;
    	    case Events.RESOURCE_REMOVED :
    		structListener.resourceRemoved(evt);
    		break;
    	    case Events.RESOURCE_UNLOADED :
    		structListener.resourceUnloaded(evt);
    		break;
    	    }
    	}
        }

        /**
         * This resource is being unloaded.
         * The resource is being unloaded from memory, perform any additional
         * cleanup required.
         */
        public void notifyUnload() {
    	fireStructureChangedEvent(Events.RESOURCE_UNLOADED);
    	super.notifyUnload();
        }
        /**
         * Delete this Resource instance, and remove it from its store.
         * This method will erase definitely this resource, for ever, by removing
         * it from its resource store (when doable).
         */
        public synchronized void delete()
    	throws MultipleLockException
        {
    	disableEvent();
    	// fire and not post because we don't want this resource
    	// to be locked() during the delete.
    	fireStructureChangedEvent(Events.RESOURCE_REMOVED);
    	ResourceFrame frames[] = getFrames();
    	if ( frames != null ) {
    	    for (int i = 0 ; i < frames.length ; i++) {
    		if ( frames[i] == null )
    		    continue;
    		frames[i].removeFrameEventListener(this);
    		this.removeAttributeChangedListener(frames[i]);
    		frames[i].unregisterResource(this);
    	    }
    	}
    	try {
    	    super.delete();
    	} catch (MultipleLockException ex) {
    	    enableEvent();
    	    throw ex;
    	}
        }

        /**
         * Mark this resource as having been modified.
         */
        public void markModified() {
    	super.markModified();
    	postStructureChangedEvent(Events.RESOURCE_MODIFIED);
        }

        /**
         * Set some of this resource attribute. We overide setValue to post
         * events.
         */
        public synchronized void setValue(int idx, Object value) {
    	super.setValue(idx, value) ;
    	if (idx != ATTR_LAST_MODIFIED) {
    	    postAttributeChangeEvent(idx, value);
    	    postStructureChangedEvent(Events.RESOURCE_MODIFIED);
    	}
        }

        /**
         * FIXME doc
         */
        public boolean lookup(LookupState ls, LookupResult lr)
    	throws ProtocolException
        {
    	ResourceFrame frames[] = getFrames();
    	if (frames != null) {
    	    for (int i = 0 ; i < frames.length ; i++) {
    		if (frames[i] == null)
    		    continue;
    		if (frames[i].lookup(ls,lr))
    		    return true;
    	    }
    	}
    	if ( ls.hasMoreComponents() ) {
    	    // We are not a container resource, and we don't have children:
    	    lr.setTarget(null);
    	    return false;
    	} else {
    	    //we are done!
    	    lr.setTarget(getResourceReference());
    	    return true;
    	}
        }

        /**
         * Perform the request on all the frames of that resource. The
         * Reply returned is the first non-null reply.
         * @param request A RequestInterface instance.
         * @return A ReplyInterface instance.
         */
        protected ReplyInterface performFrames(RequestInterface request)
    	throws ProtocolException, NotAProtocolException
        {
    	ResourceFrame frames[] = getFrames();
    	if (frames != null) {
    	    for (int i = 0 ; i < frames.length ; i++) {
    		if (frames[i] == null)
    		    continue;
    		ReplyInterface reply  = frames[i].perform(request);
    		if (reply != null)
    		    return reply;
    	    }
    	}
    	return null;
        }

        /**
         * FIXME doc
         */
        public ReplyInterface perform(RequestInterface request)
    	throws ProtocolException, NotAProtocolException
        {
    	return performFrames(request);
        }

        /**
         * Initialize the frames of that framed resource.
         * @param values Default attribute values.
         */

        public void initialize(Object values[]) {
    	this.attrListener   = null;
    	this.structListener = null;
    	disableEvent();
    	super.initialize(values);
    	// Initialize the frames if any.
    	ResourceFrame frames[] = getFrames();
    	if ( frames != null ) {
    	    this.framesRef = new Hashtable(frames.length);
    	    Hashtable defs = new Hashtable(3);
    	    for (int i = 0 ; i < frames.length ; i++) {
    		if ( frames[i] == null )
    		    continue;
    		frames[i].registerResource(this);
    		frames[i].initialize(defs);
    		frames[i].addFrameEventListener(this);
    		this.addAttributeChangedListener(frames[i]);
    	    }
    	} else {
    	    this.framesRef = new Hashtable(3);
    	}
    	enableEvent();
        }

    }