// ResourceImpl.java
// $Id: ResourceImpl.java,v 1.8 1997/07/30 14:07:50 ylafon Exp $  
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.tools.resources.impl;

import java.io.*;
import java.util.*;
import java.beans.*;

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

public class ResourceImpl implements Resource,  
                                     java.io.Serializable,
                                     java.lang.Cloneable
{

  /**
   * The space this resource belongs to.
   */
  protected transient ResourceSpace space = null;
  /**
   * The container this resource is attached to.
   */
  protected transient ResourceReference container = null;
  /**
   * Reference to ourself, as provided by our space at init time.
   */
  protected transient ResourceReference self = null;
  /**
   * Our Resource context
   */
  protected transient ResourceContext myContext = null;
  /**
   * The property holder implementation.
   */
  protected transient PropertyHolder holder = null;
  /**
   * The delegator implementation.
   */
  protected transient ResourceDelegatorImpl delegator = null;
  /**
   * The mandatory name property.
   */
  protected String name = null;
  /**
   * modification flag
   */
  protected transient boolean modified = false;


  public Resource getClone() {
    try {
      ResourceImpl clone = (ResourceImpl)clone();
      clone.holder = holder.getClone();
      return clone;
    } catch (CloneNotSupportedException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
      return null;
    }
  }

  public ResourceContext getContext() {
    return myContext;
  }

  /**
   * use by specific Saver (ascii for example)
   */
  public ResourceReference getContainer() {
    return container;
  }

  /**
   * use by specific Saver (ascii for example)
   */
  public void setContainer(ResourceReference container) {
    this.container = container;
  }

  /**
   * use by specific Saver (ascii for example)
   */
  public PropertyHolder getPropertyHolder() {
    return holder;
  }

  /**
   * List all available attribute names of that resource.
   * The DefaultResource relies on the Java reflection API to implement this
   * method. However, one can change that default implementation for 
   * preventing a beans <em>property</em> to be seen as a resource
   * <em>attribute</em>. This would also require changing the default
   * implementation for attribute value accessors.
   * @return An enumeration of PropertyDescriptor instances.
   * @exception SecurityException If that access is denied.
   * @see java.beans.PropertyDescriptor
   */
  public Enumeration getProperties() {
    return holder.getProperties();
  }
  
  /**
   * 
   */
  public void addDelegatee(ResourceReference resource){
    modified = true;
    delegator.addDelegatee(resource);
    try {
      System.out.println("*********** DELEGATEE ADDED : "+
			 resource.lock().getName()+
			 "*********");
    } finally {
      resource.unlock();
    }
  }

  /**
   * 
   */
  public void removeDelegatee(ResourceReference resource){
    modified = true;
    delegator.removeDelegatee(resource);
  }

  /**
   * That's the part of it that will be in Java Beans some days
   */
  public Object getInstanceOf(Class delegateInterface){
    return delegator.getInstanceOf(delegateInterface);
  }

  /**
   * 
   */
  public boolean isInstanceOf(Class delegateInterface){
    return delegator.isInstanceOf(delegateInterface);
  }

  /**
   * 
   */
  public ResourceReference[] getDelegatees(){
    return delegator.getDelegatees();
  }

  /**
   * Get an attribute value.
   * The DefaultResource class provides a default implementation for that
   * method that relies on Java reflection's API (it will search for
   * a <em>name</em> property and invoke its <code>reader</code> method
   * to access the attribute's value.
   * @param name The attribute's name.
   * @return The attribute value, conforming to the type specified
   * by the beans property description.
   * @exception IllegalAccessException If the given attribute is not
   * supported by the resource.
   * @exception SecurityException If that access is denied.
   */
  public Object getValue(String name)
    throws IllegalAccessException, IllegalArgumentException
  {
    return holder.getValue(name);
  }
  
  /**
   * Get a set of attribute values.
   * This version of <code>getValue</code> will fetch simultaneously
   * several property values. It can be thought of as a series of call
   * to the single <code>getValue</code> method, but has two additional 
   * properties:
   * <ul>
   * <li>It is <em>atomic</em>,
   * <li>It is likely to be more efficent.
   * </ul>
   * @param names The set of attribute names whose value is to be fetched.
   * @return An array of values.
   * @exception IllegalAccessException if one of the attribute's name
   * is invalid.
   * @exception SecurityException If that access is denied.
   */
  public Object[] getValues(String names[]) 
    throws IllegalAccessException, IllegalArgumentException
  {
    return holder.getValues(names);
  }
  
  /**
   * Set an attribute value.
   * The DefaultResource class provides a default implementation for that
   * method by relying on Java reflection's API (see the <code>getValue
   * </code> method description).
   * @param name The name of the attribute whose value is to be changed.
   * @param value The new value for that attribute.
   * @exception IllegalAccessException If the attribuite doesn't exist.
   * @exception IllegalArgumentException If the provided value doesn't
   * work as a value for this attribute.
   * @exception SecurityException If that access is denied.
   */
  public void setValue(String name, Object value) 
    throws IllegalAccessException, IllegalArgumentException
  {
    modified = true;
    holder.setValue(name, value);
  }
  
  /**
   * Set a number of attribute values at once.
   * This bears the same relationship with <code>setValue</code> than
   * <code>getValue</code> to <code>getValues</code>.
   * The changes are guaranteed to be either all performed, or none of them
   * if performed. For efficiency reasons, the provided <em>values</em>
   * parameter <strong>may</strong> be modified by the method.
   * @param names The attribute names,
   * @param values The new values for these attributes.
   * @exception IllegalAccessException If one of the attribute doesn't
   * exist.
   * @exception IllegalArgumentException If one of the value is invalid.
   * @exception SecurityException If that access is denied.
   */
  public void setValues(String names[], Object values[]) 
    throws IllegalAccessException, IllegalArgumentException
  {
    modified = true;
    holder.setValues(names, values);
  }

  
  public void addAttributeChangedListener(AttributeChangedListener l) {
    holder.addAttributeChangedListener(l);
  }
  
  public void removeAttributeChangedListener(AttributeChangedListener l) {
    holder.removeAttributeChangedListener(l);
  }

  /**
   * Get the name of that resource.
   * A resource is <strong>required</strong> to have a <em>name</em>.
   * @return The name of the resource, as a String.
   * @exception SecurityException If that access is denied.
   */
  public String getName() {
    return (String) holder.protectedGetValue("name");
  }

  /**
   * Set the name of that resource.
   * A resource is <strong>required</strong> to have a <em>name</em>.
   * @param name The new name for that resource.
   * @exception SecurityException If that access is denied.
   */
  public void setName(String name) 
    throws IllegalArgumentException
  {
    holder.protectedSetValue("name", name);
  }

  
  public synchronized String igetName() // to change
    throws IllegalAccessException, IllegalArgumentException
  {
    return name;
  }

  public synchronized void isetName(String name) // to change
    throws IllegalAccessException, IllegalArgumentException
  {
    this.name = name;
  }

  /**
   * Create that resource and attach it to the given space.
   * A resource is <em>created</em> by calling this method right after
   * the new instance has finished. A resource is always created once,
   * but can be initialized as many times as it is loaded.
   * @param context The context to create the resource.
   * @exception ResourceInitException If the resource cannot be created,
   * either because of invalid default values, or because it cannot be 
   * attached to the space.
   */
  public void create(ResourceContext context) 
    throws ResourceInitException
  {
    // Normal initialization:
    this.myContext = context;
    this.space     = context.getResourceSpace();
    if (holder == null)
      holder = space.getPropertyHolder( new ProtectedInvoker(), this);
    // Set default attribute values:
    String defnames[] = context.getPropertyNames();
    if ( defnames != null ) {
      try {
	// take only the properties that I need
	Enumeration e = getProperties();
	Hashtable names = new Hashtable(20);
	int length = 0;
	//FIXME
	while (e.hasMoreElements()) {
	  names.put(((PropertyDescriptor)e.nextElement()).getName(), "");
	  length++;
	}
	Object PropertyValues [] = context.getPropertyValues();
	String ndefnames [] = new String[length];
	Object nvalues [] = new Object[length];
	int defptr = 0; int ndefptr = 0;
	while (defptr < defnames.length) {
	  if (names.get(defnames[defptr]) != null) {
	    if (!(ndefptr < length))
	      throw new ResourceInitException("index out of range in init.");
	    ndefnames [ndefptr] = defnames[defptr];
	    nvalues [ndefptr] = PropertyValues[defptr];
	    ndefptr++;
	  }
	  defptr++;
	}
	String realdefnames [] = new String[ndefptr];
	Object realvalues [] = new Object[ndefptr];
	System.arraycopy(ndefnames,0,realdefnames,0,ndefptr);
	System.arraycopy(nvalues,0,realvalues,0,ndefptr);
	holder.setValues(realdefnames, realvalues);
      } catch (Exception ex) {
	System.out.println(ex.getMessage());
	ex.printStackTrace();
	throw new ResourceInitException(ex.getMessage());
      }
    }
    space.resourceCreated(this);
    //    init(context);
  }

  /**
   * Destroy that resource, for ever.
   * @exception SecurityException If that access is denied.
   */
  public void destroy() {
    if (container != null) {
	try {
	  ResourceContainerImpl cont = (ResourceContainerImpl)container.lock();
	  cont.removeFromCache(getName());
	} finally {
	  container.unlock();
	}
      }
    space.delete(this);
    space.resourceDeleted(this);
  }
  
  /**
   * Initialize that resource within the given context.
   * A resource implementor is <strong>guaranteed</strong> that this method
   * will get called after the instance carries its proper attribute
   * settings and before any other access to the resource is performed.
   * @param context The context in which that resource is to initialize
   * itself.
   * @exception ResourceInitException If the resource is not able to 
   * initialize itself in the given context.
   * @exception SecurityException If that access is denied.
   */
  public void init(ResourceContext context) 
    throws ResourceInitException
  {
    this.myContext = context;    
    this.self      = context.getResourceReference();
    this.space     = context.getResourceSpace();
    this.container = context.getContainer();
    this.delegator = new ResourceDelegatorImpl(self);
    if (holder == null)
      holder = space.getPropertyHolder( new ProtectedInvoker(), this);
    space.resourceInited(this);
  }

  /**
   * Do you want to unload yourself ?
   * Check wether this resource is willing to be persistified back to disk.
   * If the resource agrees to be unloaded, then it should mark itself as
   * no longer valid. The caller is than free to take any appropriate action
   * to unload that resource from memory (provided it can guarantee that no
   * one else in the system is using it).
   * @return A boolean, <strong>true</strong> if the resource can be
   * unloaded <strong>false</strong> otherwise.
   */
  public synchronized boolean unload() {
System.out.println("unloading "+this);
    try {
      if (modified){
	space.save(this);
	modified = false;
      }
      if (container != null) {
	try {
	  ResourceContainerImpl cont = (ResourceContainerImpl)container.lock();
	  cont.removeFromCache(getName());
	} finally {
	  container.unlock();
	}
      }
      space.resourceUnloaded(this);
      return true;
    } catch (ChildNotSupportedException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
      return false;
    }
  }
  

  public void setSaved() {
    modified = false;
  }

  /**
   * Resolve the given state.
   * If the state has more components, it is up to the resource to consume
   * the next available component and map it to one of its children, 
   * otherwise the resource must check against whatever security mechanism
   * it uses, wether the access should be granted.
   * @param state The lookup state instance to resolve.
   * @exception SecurityException If that access is denied.
   * @see ResourceSpace
   */
  public ResourceReference resolve(LookupState state) {
    System.out.println("resolving : "+getName());
    if ( state.hasMoreComponents() )
      return null;
    return self;
  }

  // This inner class is used to provide to our holder implementation
  // access to the protected method used to set/get property values.
  // Extremelly cool on one hand, probably overkill on the other
  // See impl.Runner

  class ProtectedInvoker implements Runner {

      public Object run(java.lang.reflect.Method m, Object args[]) 
	  throws IllegalAccessException
		 , java.lang.reflect.InvocationTargetException
      {
	  System.out.println("invoke : "+m.getName()+"("
			     + ((args != null && args.length > 0) 
				? args[0].toString() : "<null>")+
			     ")");
	  return m.invoke(ResourceImpl.this, args);
      }
      
  }
  
  public ResourceImpl() {
  }

  static final long serialVersionUID = -2353612949714413966L;
  
}
