// FramedResource.java
// $Id: FramedResource.java,v 1.30 2004/07/20 15:54:30 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.EventObject;
import java.util.Hashtable;

import java.io.PrintStream;

import org.w3c.tools.resources.event.AttributeChangedEvent;
import org.w3c.tools.resources.event.AttributeChangedListener;
import org.w3c.tools.resources.event.Events;
import org.w3c.tools.resources.event.FrameEvent;
import org.w3c.tools.resources.event.FrameEventListener;
import org.w3c.tools.resources.event.ResourceEvent;
import org.w3c.tools.resources.event.ResourceEventMulticaster;
import org.w3c.tools.resources.event.ResourceEventQueue;
import org.w3c.tools.resources.event.StructureChangedEvent;
import org.w3c.tools.resources.event.StructureChangedListener;

/**
 * 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;
	String            identifier = 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, identifier);
	}

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

	public Resource unsafeLock()
	    throws InvalidResourceException 
	{
	    FramedResource res = (FramedResource)framedr.lock();
	    lockCount++;
	    return res.unsafeGetFrame(frameClass, identifier);
	}

	/**
	 * 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;
	    this.identifier = rframe.getIdentifier();
	}
    }

    /**
     * Debug flag
     */
    protected final boolean debugEvent = false;

    /**
     * Do we handle events?
     */
    protected boolean event_disabled = false;

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

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

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

    protected void disableEvent() {
	event_disabled = true;
    }

    protected void enableEvent() {
	event_disabled = false;
    }

    protected boolean eventDisabled() {
	return event_disabled;
    }

    /**
     * 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 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) {
	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) {
	if (debugEvent) 
	    displayEvent( this, evt );
	if (! isUnloaded()) 
	    markModified();
    }

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

    public void frameModified(FrameEvent evt) {
	if (debugEvent) 
	    displayEvent( this, evt );
	if (! isUnloaded()) 
	    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) {
	if (debugEvent) 
	    displayEvent( this, evt );
	if (! isUnloaded()) 
	    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(id , 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;
    }

    /**
     * Get the frame of the given class and identifier.
     * @param cls The class of frames we are looking for.
     * @param identifier the frame identifier
     * @return a ResourceFrame instance of <strong>null</strong>
     */
    public synchronized ResourceFrame getFrame(Class c, String identifier) {
	ResourceFrame frames[] = collectFrames(c);
	if (frames != null) {
	    for (int i = 0 ; i < frames.length ; i++) {
		ResourceFrame fr = frames[i];
		if (fr.getIdentifier().equals(identifier))
		    return fr;
	    }
	}
	return null;
    }

    /**
     * Get the frame of the given class and identifier.
     * @param cls The class of frames we are looking for.
     * @param identifier the frame identifier
     * @return a ResourceFrame instance of <strong>null</strong>
     */
    ResourceFrame unsafeGetFrame(Class c, String identifier) {
	ResourceFrame frames[] = collectFrames(c);
	if (frames != null) {
	    for (int i = 0 ; i < frames.length ; i++) {
		ResourceFrame fr = frames[i];
		if (fr.getIdentifier().equals(identifier))
		    return fr;
	    }
	}
	return null;
    }

    /**
     * Get the frame of the given class.
     * @param classname the class name
     * @return a ResourceFrame instance of null.
     */
    public synchronized ResourceFrame getFrame(String classname) {
	try {
	    Class c = Class.forName(classname);
	    return getFrame(c);
	} catch (Exception ex) {
	    return null;
	}
    }

    /**
     * (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 (eventDisabled())
	    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 (eventDisabled())
	    return;
	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) {
	    ResourceReference resref = unsafeGetResourceReference();
	    if (resref != null) {
		StructureChangedEvent evt = 
		    new StructureChangedEvent(resref, 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() {
	//
	// direct notification
	//
	ResourceFrame frames[] = unsafeGetFrames();
	if ( frames != null ) {
	    for (int i = 0 ; i < frames.length ; i++) {
		if ( frames[i] == null )
		    continue;
		frames[i].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).
     * @exception MultipleLockException if someone has locked this resource.
     */
    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);
	}
    }

    /**
     * Set a value, without posting event.
     * @param idx The attribute index, in the list of attributes advertized by 
     * the resource.
     * @param value The new value for this attribute.
     */
    public synchronized void setSilentValue(int idx, Object value) {
	disableEvent();
	super.setValue(idx, value);
	enableEvent();
    }

    /**
     * Set a value, without posting event.
     * @param name The attribute name.
     * @param value The new value for the attribute.
     */
    public synchronized void setSilentValue(String name, Object value) {
	disableEvent();
	super.setValue(name, value);
	enableEvent();
    }

    /**
     * Lookup the target resource.
     * @param ls The current lookup state
     * @param lr The result
     * @return true if lookup is done.
     * @exception ProtocolException If an error relative to the protocol occurs
     */
    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.
     * @exception ProtocolException If an error relative to the protocol occurs
     * @exception ResourceException If an error not relative to the 
     * protocol occurs
     */
    protected ReplyInterface performFrames(RequestInterface request) 
	throws ProtocolException, ResourceException
    {
	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;
    }

    /**
     * Perform the request.
     * @return a ReplyInterface instance
     * @exception ProtocolException If an error relative to the protocol occurs
     * @exception ResourceException If an error not relative to the 
     * protocol occurs
     */
    public ReplyInterface perform(RequestInterface request) 
	throws ProtocolException, ResourceException
    {
	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(Math.max(frames.length, 1));
	    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();
    }

}
