// FramedResource.java
// $Id: FramedResource.java,v 1.9 1998/03/09 14:03:20 bmahe 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 {

    ResourceFrame     rframe  = 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 
    {
	framedr.lock();
	lockCount++;
	return rframe;
    }

    /**
     * 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.rframe  = rframe;
      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.attrListener   = null;
    clone.structListener = null;
    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.
   * @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);
      postEvent(evt);
      //attrListener.attributeChanged(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;
      }
    }
  }

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

}
