// PropertyHolderImpl.java
// $Id: PropertyHolderImpl.java,v 1.2 1997/07/30 14:07:11 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.util.*;
import java.lang.reflect.*;
import java.beans.*;

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

/**
 * This class implements attribute value holding.
 * More precisely, it supports accesses to attributes: description, read
 * and write access along with support for attribute change events.
 * This works by examining the target resource through the 
 * <code>AttributeHolderInspector</code> class for deducing the list of
 * available attributes of the target.
 * <p>Note that this implementation makes no assumption as to how the
 * attribute values are fetched or stored.
 * @see AttributeHolderInspector
 */

public class PropertyHolderImpl implements PropertyHolder , 
                                           java.lang.Cloneable
{
    /**
     * The resource we are managing attributes for.
     */
    protected transient Resource target = null;
    /**
     * The attribute descriptors for that resource.
     */
    protected transient Hashtable attrs = null;
    /**
     * Attribute change listener support.
     */
  protected transient AttributeChangedListener attrListener = null;
  protected transient Runner runner = null;
  

  public PropertyHolder getClone() {
    try {
      return (PropertyHolderImpl)clone();
    } catch (CloneNotSupportedException ex) {
      System.out.println(ex.getMessage());
      ex.printStackTrace();
      return null;
    }
  }
  
    /**
     * Fire an attribute change event.
     * @param name The name of the attribute that has changed.
     * @param oldvalue The old value for the attribute.
     * @param newvalue The new value for that attribute.
     */

    protected void fireAttributeChangeEvent(String attr
					    , Object oldvalue
					    , Object newvalue) {
	if ( attrListener != null ) {
	    AttributeChangedEvent evt = new AttributeChangedEvent(target
								  , attr
								  , oldvalue
								  , newvalue);
	    attrListener.attributeChanged(evt);
	}
    }

    /**
     * Set any attribute, catching any exception.
     */

    public void protectedSetValue(String name, Object value) {
	try {
	    setValue(name, value);
	} catch (Exception ex) {
	    ex.printStackTrace();
	    throw new InternalError("can't set \""+name+"\"");
	}
    }

    public Object protectedGetValue(String name) {
	try {
	    return getValue(name);
	} catch (Exception ex) {
	    ex.printStackTrace();
	    throw new InternalError("can't get \""+name+"\"");
	}
    }

    /**
     * Print all the attributes holded by this implementation.
     * @return A String instance.
     */

    public String toString() {
	StringBuffer sb = new StringBuffer();
	sb.append('[');
	// List the resource's class, and its frames:
	sb.append(target.getName());
	sb.append(' ');
	sb.append(target.getClass().getName());
	// List all atttributes, and their values:
	Enumeration        e = getProperties();
	PropertyDescriptor p = null;
	try {
	    while ( e.hasMoreElements() ) {
		p = (PropertyDescriptor) e.nextElement();
		sb.append(' ');
		sb.append(p.getName());
		sb.append('=');
		sb.append(getValue(p.getName()));
	    }
	} catch (Exception ex) {
	    throw new InternalError("unaccessible declared attribute "
				    + ((p != null) ? p.getName() : "??"));
	}
	sb.append(']');
	return sb.toString();
    }

    /**
     * Enumerate the attributes property descriptors.
     * @return An enumeration of PropertyDescriptor.
     * @see PropertyDescriptor
     */

    public Enumeration getProperties() {
	return attrs.elements();
    }
    /**
     * Get an attribute value.
     * @param name The attribute name.
     * @return The attribute value.
     * @exception IllegalAccessException If the attribute isn't supported,
     * @exception IllegalArgumentException If the provided value doesn't match
     * the attribute type.
     */

    public synchronized Object getValue(String name) 
	throws IllegalAccessException, IllegalArgumentException
    {
	PropertyDescriptor p = (PropertyDescriptor) attrs.get(name);
	if ( p == null )
	    throw new IllegalAccessException(name);
	// Get the method reader, apply and return result:
	try {
	    return runner.run(p.getReadMethod(), null);
	} catch (InvocationTargetException ex) {
	    throw new IllegalArgumentException(ex.getMessage());
	}
    }

    /**
     * Get a set of attribute values.
     * @param names The name of the attributes to fetch.
     * @return An array of attribute values.
     * @exception IllegalAccessException If one of the attribute isn't
     * supported,
     * @exception IllegalArgumentException If one of the provided value
     * doesn't match the attribute type.
     */

    public synchronized Object[] getValues(String names[]) 
	throws IllegalAccessException, IllegalArgumentException
    {
	PropertyDescriptor p        = null;
	Object             values[] = new Object[names.length];
	try {
	    for (int i = 0 ; i < names.length ; i++) {
		if ((p = (PropertyDescriptor) attrs.get(names[i])) == null)
		    throw new IllegalAccessException(names[i]);
		values[i] = runner.run(p.getReadMethod(), null);
	    }
	} catch (InvocationTargetException ex) {
	    throw new IllegalArgumentException(ex.getMessage());
	}
	return values;
    }
    
    /**
     * Set an attribute value.
     * @param name The name of the attribute.
     * @param value The new value for that attribute.
     */

    public synchronized void setValue(String name, Object newvalue)
	throws IllegalAccessException, IllegalArgumentException
    {
	// Get the corresponding property:
	PropertyDescriptor p = (PropertyDescriptor) attrs.get(name);
	if ( p == null )
	    throw new IllegalAccessException(name);
	// Perform setting, notify change:
	Object oldvalue = null;
	try {
	    Method rm     = p.getReadMethod();
	    Method wm     = p.getWriteMethod();
	    oldvalue      = runner.run(rm, null);
	    // Is the value really about to change ?
	    if (((oldvalue == null) && (newvalue == null))
		|| ((oldvalue != null) && oldvalue.equals(newvalue)))
		return;
	    // Ok, make the change:
	    Object args[] = new Object[1];
	    args[0]       = newvalue;
	    runner.run(wm, args);
	} catch (InvocationTargetException ex) {
	    ex.printStackTrace();
	    throw new IllegalArgumentException(ex.getMessage());
	}
	fireAttributeChangeEvent(name, oldvalue, newvalue);
    }

    /**
     * Perform a set of attribute changes atomically.
     * @param names The attribute names.
     * @param values The attribute values.
     */

    public synchronized void setValues(String names[], Object newvalues[])
	throws IllegalAccessException
    {
	PropertyDescriptor p = null;
	Object args[]        = new Object[1];
	Object oldvalues[]   = new Object[newvalues.length];
	int    idx           = 0;
	try {
	    // Apply changes:
	    while ( idx < names.length ) {
		if ((p = (PropertyDescriptor) attrs.get(names[idx])) == null)
		    throw new IllegalAccessException(names[idx]);
		Method rm      = p.getReadMethod();
		Method wm      = p.getWriteMethod();
		oldvalues[idx] = runner.run(rm, null);
		args[0]        = newvalues[idx];
		runner.run(wm, args);
		idx++;
	    }
	} catch (InvocationTargetException ex) {
	  throw new IllegalArgumentException(ex.getMessage());
	} finally {
	    if ( idx != names.length ) {
		// Undo changes
		try {
		    for (int i = 0 ; i < idx ; i++) {
			p = (PropertyDescriptor) attrs.get(names[i]);
			Method wm = p.getWriteMethod();
			args[0]   = oldvalues[i];
			runner.run(wm, args);
		    }
		} catch (Exception ex) {
		    throw new RuntimeException("unable to undo changed");
		}
	    }
	}
	// Fire change events, now that all changes are done:
	for (int i = 0 ; i < newvalues.length ; i++) {
	    if ((oldvalues[i] != null) && oldvalues[i].equals(newvalues[i]) )
		continue;
	    fireAttributeChangeEvent(names[i], oldvalues[i], newvalues[i]);
	}
    }

    /**
     * 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);
    }

    /**
     * Create an attribute holder implementation for the given resource.
     * @param target The target resource to implement attribute holding for.
     */
    
    public PropertyHolderImpl(Runner runner, Resource target) {
	this.target = target;
	this.runner = runner;
	this.attrs  = PropertyHolderInspector.getProperties(target.getClass());
    }

}
