/*
 * @(#)XPolynomial.java    2.4.0   14 July 2005
 *
 * Copyright 2005
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package  edu.neu.ccs;

import edu.neu.ccs.gui.*;
import edu.neu.ccs.util.*;

import java.text.ParseException;
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
import java.io.Serializable;

/**
 * <p>Class <code>XPolynomial</code> provides an implementation
 * of a polynomial in one real variable with real coefficients.</p>
 *
 * <p><code>XPolynomial</code> fits into the family of functions
 * that implement <code>Function.OneArg</code>.</p>
 *
 * <p>For convenience of data entry, <code>XPolynomial</code> also
 * implements <code>Stringable</code>.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @since   2.4.0
 */
public class XPolynomial
    implements Function.OneArg, Parameter.ArrayParam, Stringable,
        JPTConstants, Serializable
{
    /** The standard error message for fromStringData. */
    public static final String standardMessage =
        "\nXPolynomial Error: Data format must be\n"
        + "[...;...;...;etc] or\n"
        + "[c0=...;c1=...;c2=...;etc]\n"
        + "where ... stands for the polynomial coefficients\n"
        + "in increasing order starting at index 0\n"
        + "from left to right\n";
    
    
    /** Division by zero error message. */
    private static String divisionMessage =
        "Division by zero in class XPolynomial";
    
    
    /**
     * The polynomial coefficients or <code>null</code> if no
     * storage has been allocated.
     */
    protected double[] coefficients = null;
    
    
    /** Helper object for property change API. */
    protected SwingPropertyChangeSupport changeAdapter =
        new SwingPropertyChangeSupport(this);
    
    
    /**
     * <p>The constructor that sets the polynomial to the zero
     * polynomial but allocates no storage for future coefficients.</p>
     */
    public XPolynomial() { }
    
    
    /**
     * <p>The constructor that sets the polynomial to the zero
     * polynomial and allocates storage with the given capacity.</p>
     *
     * @param capacity the desired storage capacity
     */
    public XPolynomial(int capacity) {
        setCapacity(capacity);
    }
    
    
    /**
     * <p>The constructor that sets the polynomial coefficients to a
     * copy of the given params array.</p>
     *
     * @param params the array of polynomial coefficients to copy
     */
    public XPolynomial(double[] params) {
        setCoefficients(params);
    }
    
    
    /**
     * <p>Constructs and initializes an <code>XPolynomial</code> from the
     * specified XPolynomial object.</p>
     *
     * @param polynomial the <code>XPolynomial</code> object to copy
     */
    public XPolynomial(XPolynomial polynomial) {
        setCoefficients(polynomial);
    }
    
    
    /**
     * The constructor that uses <code>fromStringData</code>
     * to set the polynomial coefficients.
     */
    public XPolynomial(String data)
        throws ParseException
    {
        fromStringData(data);
    }
    
    
    /**
     * The constructor that uses <code>fromStringArrayData</code>
     * to set the polynomial coefficients.
     */
    public XPolynomial(String[] strings)
        throws ParseException
    {
        fromStringArrayData(strings);
    }
    
    
    /**
     * <p>Sets the polynomial to the zero polynomial, that is,
     * the polynomial that evaluates to zero everywhere.</p>
     *
     * <p>Fires property change VALUE.</p>
     */
    public void setPolynomialToZero() {
        if (coefficients != null) {
            coefficients = null;
            
            changeAdapter.firePropertyChange(VALUE, null, null);
        }
    }
    
    
    /**
     * <p>Returns the degree of the polynomial which is defined
     * to be the highest index whose coefficient is non-zero;
     * by convention, the zero polynomial has degree -1.</p>
     */
    public int getDegree() {
        if (coefficients == null)
            return -1;
        
        int d = coefficients.length - 1;
        
        while ((d >= 0) && (coefficients[d] == 0))
            d--;
        
        return d;
    }
    
    
    /**
     * <p>Returns the capacity of the current internal storage,
     * that is, the largest index into which data may be stored
     * without reallocation of the coefficients array.</p>
     *
     * <p>Returns -1 if no storage is currently allocated.</p>
     */
    public int getCapacity() {
        if (coefficients == null)
            return -1;
        
        return coefficients.length - 1;
    }
    
    
    /**
     * <p>Sets the capacity of this polynomial to the given value
     * except that any negative value will cause the polynomial
     * storage to be deallocated and the capacity will be -1.</p>
     *
     * <p>If the capacity is set to a value less than the degree,
     * then information will be lost.</p>
     *
     * <p>Fires property change VALUE if information is lost
     * due to a call of this method.</p>
     *
     * @param capacity the desired storage capacity
     */
    public void setCapacity(int capacity) {
        if (capacity < 0)
            capacity = -1;
        
        if (getCapacity() == capacity)
            return;
        
        int d = getDegree();
        
        if (capacity < 0) {
            coefficients = null;
        }
        else {
            int size = capacity + 1;
            
            double[] data = new double[size];
            
            if (coefficients != null) {
                size = Math.min(size, coefficients.length);
                
                for (int i = 0; i < size; i++)
                    data[i] = coefficients[i];
            }
            
            coefficients = data;
        }
        
        // if information lost
        if (capacity < d) {
            // notify listeners of property change
            changeAdapter.firePropertyChange(VALUE, null, null);
        }
    }
    
    
    /**
     * <p>Sets the capacity to the degree.</p>
     *
     * <p>Equivalent to <code>setCapacity(getDegree())</code>.</p>
     */
    public void shrinkCapacity() {
        setCapacity(getDegree());
    }
    
    
    /**
     * <p>Returns a copy of the polynomial coefficients.</p>
     *
     * <p>Returns an array of size (d+1) where d is the degree.
     * If the degree is -1, this array is empty but is not
     * <code>null</code>.</p>
     */
    public double[] getCoefficients() {
        int d = getDegree();
        
        double[] values = new double[d+1];
        
        for (int i = 0; i <= d; i++)
            values[i] = coefficients[i];
        
        return values;
    }
    
    
    /**
     * <p>Sets the coefficients of the polynomial to a copy of
     * the data in the given params array.</p>
     *
     * <p>If the given params is <code>null</code>, sets this
     * polynomial to the zero polynomial.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param params the array of polynomial coefficients to copy
     */
    public void setCoefficients(double[] params) {
        if ((params == null) || (params.length == 0)) {
            coefficients = null;
        }
        else {
            int size = params.length;
            
            coefficients = new double[size];
            
            for (int i = 0; i < size; i++)
                coefficients[i] = params[i];
        }
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Sets the coefficients of this polynomial to a copy of
     * the data in the given <code>XPolynomial</code>.</p>
     *
     * <p>If the given polynomial is <code>null</code>, sets this
     * polynomial to the zero polynomial.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param polynomial the <code>XPolynomial</code> object to copy
     */
    public void setCoefficients(XPolynomial polynomial) {
        if (polynomial == null)
            setPolynomialToZero();
        else
            setCoefficients(polynomial.getCoefficients());
    }
    
    
    /**
     * <p>Returns the coefficient at the given index.</p>
     *
     * <p>Returns zero if the index is invalid.</p>
     *
     * @param index the index of the coefficient
     */
    public double getCoefficient(int index) {
        if (index < 0)
            return 0;
        
        int capacity = getCapacity();
        
        if (index > capacity)
            return 0;
        
        return coefficients[index];
    }
    
    
    /**
     * <p>Sets the coefficient at the given index to the given value.</p>
     *
     * <p>Does nothing if the index is less than zero.</p>
     *
     * <p>If the index is greater than the capacity, then the capacity
     * is increased to accomodate the index.</p>
     *
     * <p>Technical note: If several coefficients are to be set via this
     * method, it is most efficient to set the highest index first since
     * that will guarantee enough capacity for all other assignments.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param index the index of the coefficient
     * @param value the value of the coefficient
     */
    public void setCoefficient(int index, double value) {
        if (index < 0)
            return;
        
        int capacity = getCapacity();
        
        if (index > capacity)
            setCapacity(index);
        
        coefficients[index] = value;
        
        // notify listeners of property change
        changeAdapter.firePropertyChange(VALUE, null, null);
    }
    
    
    /**
     * <p>Returns the maximum of the absolute value of the coefficients in
     * this polynomial.</p>
     */
    public double maxCoefficient() {
        int d = getDegree();
        
        if (d == -1)
            return 0;
        
        double max = Math.abs(coefficients[d]);
        
        for (int i = 0; i < d; i++)
            max = Math.max(max, Math.abs(coefficients[i]));
        
        return max;
    }
    
    
    /** Returns true if the polynomial is the zero polynomial. */
    public boolean isZero() {
        return (getDegree() == -1);
    }
    
    
    /**
     * <p>Returns true if the polynomial is almost the zero polynomial
     * relative to the measure epsilon, that is, if all of its
     * coefficients have absolute value less than or equal epsilon.</p>
     *
     * <p>Replaces epsilon with its absolute value before testing.</p>
     *
     * <p>Remark: If the polynomial has degree d and is almost zero to
     * within epsilon, then for x with absolute value at most one:</p>
     *
     * <p><pre>  abs(evaluate(x)) &lt;= (d + 1) * epsilon</pre></p>
     *
     * <p>In other words, a polynomial that is almost zero to within
     * epsilon has quite small values for arguments x at most one.</p>
     *
     * @param epsilon the measure of closeness to zero
     */
    public boolean isAlmostZero(double epsilon) {
        int d = getDegree();
        
        if (d == -1)
            return true;
        
        epsilon = Math.abs(epsilon);
        
        for (int i = 0; i <= d; i++)
            if (Math.abs(coefficients[i]) > epsilon)
                return false;
        
        return true;
    }
    
    
    /**
     * <p>Returns true if the given polynomial is equal to <code>this</code>
     * in the sense that the degrees are the same and all coefficients agree.</p>
     *
     * <p>If the given polynomial is <code>null</code> then returns true
     * if and only if <code>this</code> equals the zero polynomial.</p>
     *
     * @param p a polynomial
     */
    public boolean isEqualTo(XPolynomial p) {
        if (p == null)
            return isZero();
        
        int d = getDegree();
        int e = p.getDegree();
        
        if (e != d)
            return false;
        
        for (int i = 0; i <= d; i++)
            if (coefficients[i] != p.coefficients[i])
                return false;
        
        return true;
    }
    
    
    /**
     * <p>Returns true if the given polynomial is almost equal to this polynomial
     * in the sense that all coefficients agree to within the measure epsilon.</p>
     *
     * <p>Equivalently, returns true if the difference between <code>this</code>
     * and the given polynomial is almost zero to within the measure epsilon.</p>
     *
     * <p>If the given polynomial is <code>null</code> then returns true
     * if and only if <code>this</code> is almost zero to within epsilon.</p>
     *
     * @param p a polynomial
     * @param epsilon the measure of closeness
     */
    public boolean isAlmostEqualTo(XPolynomial p, double epsilon) {
        if (p == null)
            return isAlmostZero(epsilon);
        
        epsilon = Math.abs(epsilon);
        
        int d = getDegree();
        int e = p.getDegree();
        
        int m = Math.min(d,e);
        
        for (int i = 0; i <= m; i++)
            if (Math.abs(coefficients[i] - p.coefficients[i]) > epsilon)
                return false;
        
        for (int i = (m+1); i <= d; i++)
            if (Math.abs(coefficients[i]) > epsilon)
                return false;
        
        for (int i = (m+1); i <= e; i++)
            if (Math.abs(p.coefficients[i]) > epsilon)
                return false;
        
        return true;
    }
    
    
    /**
     * <p>Returns the value of the polynomial at the given x.</p>
     *
     * <p>Implements <code>Function.OneArg</code>.</p>
     *
     * @param x the position at which to evaluate the polynomial
     */
    public double evaluate(double x) {
        int d = getDegree();
        
        if (d < 0)
            return 0;
        
        if (x == 0)
            return coefficients[0];
        
        double result = coefficients[d];
        
        for (int i = d - 1; i >= 0; i--) {
            result *= x;
            result += coefficients[i];
        }
        
        return result;
    }
    
    
    /**
     * <p>Sets the coefficients of the polynomial to a copy of
     * the data in the given params array.</p>
     *
     * <p>Implements <code>Parameter.ArrayParam</code>.</p>
     *
     * <p>Equivalent to <code>setCoefficients</code>.</p>
     *
     * <p>Fires property change VALUE.</p>
     *
     * @param params the array of polynomial coefficients to copy
     */
    public void setParam(double[] params) {
        setCoefficients(params);
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XPolynomial</CODE> as an annotated
     * string.</p>
     *
     * <p><code>XPolynomial[c0=...;c1=...;c2=...;etc]</code></p>
     *
     * <p>where ... stands for the polynomial coefficients
     * in increasing order from left to right.</p>
     *
     * <p>The zero polynomial is represented as:</p>
     *
     * <p><code>XPolynomial[]</code></p>
     */
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append("XPolynomial[");
        
        int d = getDegree();
        
        for (int i = 0; i <= d; i++) {
            buffer.append('c');
            buffer.append(Integer.toString(i));
            buffer.append('=');
            buffer.append(Double.toString(coefficients[i]));
            
            if (i < d)
                buffer.append(';');
        }
        
        buffer.append("]");
        
        return buffer.toString();
    }
    
    
    /**
     * <p>Returns a human readable <CODE>String</CODE> representing
     * the data state of this <CODE>XPolynomial</CODE> as a simple
     * string.</p>
     *
     * <p><code>[...;...;...;etc]</code></p>
     *
     * <p>where ... stands for the polynomial coefficients
     * in increasing order from left to right.</p>
     *
     * <p>The zero polynomial is represented as:</p>
     *
     * <p><code>[]</code></p>
     */
    public String toStringData() {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append("[");
        
        int d = getDegree();
        
        for (int i = 0; i <= d; i++) {
            buffer.append(Double.toString(coefficients[i]));
            
            if (i < d)
                buffer.append(';');
        }
        
        buffer.append("]");
        
        return buffer.toString();
    }
    
    
    /**
     * <p>Returns the data state of this <CODE>XPolynomial</CODE> as an
     * array of strings.</p>
     *
     * <p>This is a convenience method that allows to caller to use the
     * string representations of the individual coefficients in any way
     * desired.</p>
     *
     * <p>Returns an array of size (d+1) where d is the degree.
     * If the degree is -1, this array is empty but is not
     * <code>null</code>.</p>
     */
    public String[] toStringArrayData() {
        int d = getDegree();
        
        String[] strings = new String[d+1];
        
        for (int i = 0; i <= d; i++)
            strings[i] = Double.toString(coefficients[i]);
        
        return strings;
    }
    
    
    /**
     * <p>Defines the data state for this <CODE>XPolynomial</CODE> object 
     * from a <CODE>String</CODE> representation of the data state.</p>
     * 
     * <p>Accepts string data in the format output by <code>toString</code>
     * or by <code>toStringData</code>.</p>
     *
     * <p>Technical note: The parser ignores any labels of the form
     * <code>c<i>i</i>=</code> and reads only the coefficient data from
     * index 0 on up.</p>
     *
     * <p>The labels if present simply help the user track
     * the index of each coefficient as data is entered.  In particular,
     * it is not valid to omit coefficients and depend on the labels to
     * make this clear.</p>
     * 
     * <p>Fires property change VALUE.</p>
     *
     * @param data <CODE>String</CODE> representation of the data state
     * @throws ParseException if the data is malformed
     */
    public void fromStringData(String data)
        throws ParseException
    {
        if (data == null)
            throw new ParseException(standardMessage, -1);
        
        data = data.trim();
        
        String[] split = Strings.splitIn2(data);
        
        String input = split[1].trim();
        
        String[] items = Strings.trim(Strings.decode(input));
        
        fromStringArrayData(items);
    }
    
    
    /**
     * <p>Defines the data state for this <CODE>XPolynomial</CODE> object 
     * from a <CODE>String</CODE> array representation of the data state.</p>
     * 
     * <p>Each string in the array is used to defining the correspoding
     * coefficient in the polynomial.</p>
     *
     * <p>Technical note: The parser ignores any labels of the form
     * <code>c<i>i</i>=</code> and reads only the coefficient data from
     * index 0 on up.</p>
     * 
     * <p>Fires property change VALUE.</p>
     *
     * @param strings <CODE>String</CODE> array representation of the data state
     * @throws ParseException if the data is malformed
     */
    public void fromStringArrayData(String[] strings)
        throws ParseException
    {
        if (strings == null)
            throw new ParseException(standardMessage, -1);
        
        String[] values = Strings.getValues(strings);
        
        try {
            setCoefficients(Strings.stringsToDoubles(values));
        }
        catch (ParseException ex) {
            String message = ex.getMessage();
            int offset = ex.getErrorOffset();
            
            message =
                "\nXPolynomial error in term "
                + message
                + "\nat offset "
                + offset
                + "\n";
            
            throw new ParseException(message, -1); 
        }
    }
    
    
    /**
     * <p>Returns a new polynomial equivalent mathematically to (f * p),
     * that is, the polynomial obtained by multipying each coefficient
     * of p by the scale factor f.</p>
     *
     * <p>If the polynomial p is <code>null</code> or the zero polynomial
     * returns a zero polynomial.</p>
     *
     * @param f a scale factor
     * @param p a polynomial
     */
    public static XPolynomial scale(double f, XPolynomial p) {
        if (p == null)
            return new XPolynomial();
        
        int d = p.getDegree();
        
        if (d == -1)
            return new XPolynomial();
        
        XPolynomial result = new XPolynomial(d);
        
        for (int i = 0; i <= d; i++)
            result.coefficients[i] = f * p.getCoefficient(i);
        
        return result;
    }
    
    
    /**
     * <p>Returns a new polynomial equivalent mathematically to (p + q).</p>
     *
     * <p>If either polynomial is <code>null</code> or the zero polynomial
     * returns a copy of the other polynomial.</p>
     *
     * @param p a polynomial
     * @param q a polynomial
     */
    public static XPolynomial add(XPolynomial p, XPolynomial q) {
        if (p == null)
            return new XPolynomial(q);
        
        if (q == null)
            return new XPolynomial(p);
        
        int d = p.getDegree();
        int e = q.getDegree();
        
        if (d == -1)
            return new XPolynomial(q);
        
        if (e == -1)
            return new XPolynomial(p);
        
        int n = Math.max(d, e);
        
        XPolynomial result = new XPolynomial(n);
        
        for (int i = 0; i <= n; i++)
            result.coefficients[i] =
                p.getCoefficient(i) + q.getCoefficient(i);
        
        return result;
    }
    
    
    /**
     * <p>Returns a new polynomial equivalent mathematically to (p - q).</p>
     *
     * <p>If polynomial q is <code>null</code> or the zero polynomial
     * returns a copy of polynomial p.</p>
     *
     * <p>If polynomial p is <code>null</code> or the zero polynomial
     * returns a copy of the negative of polynomial q.</p>
     *
     * @param p a polynomial
     * @param q a polynomial
     */
    public static XPolynomial subtract(XPolynomial p, XPolynomial q) {
        if (p == null)
            return scale(-1, q);
        
        if (q == null)
            return new XPolynomial(p);
        
        int d = p.getDegree();
        int e = q.getDegree();
        
        if (d == -1)
            return scale(-1, q);
        
        if (e == -1)
            return new XPolynomial(p);
        
        int n = Math.max(d, e);
        
        XPolynomial result = new XPolynomial(n);
        
        for (int i = 0; i <= n; i++)
            result.coefficients[i] =
                p.getCoefficient(i) - q.getCoefficient(i);
        
        return result;
    }
    
    
    /**
     * <p>Returns a new polynomial equivalent mathematically to (p * q).</p>
     *
     * <p>If either polynomial is <code>null</code> or the zero polynomial
     * returns a zero polynomial.</p>
     *
     * @param p a polynomial
     * @param q a polynomial
     */
    public static XPolynomial multiply(XPolynomial p, XPolynomial q) {
        if ((p == null) || (q == null))
            return new XPolynomial();
        
        int d = p.getDegree();
        int e = q.getDegree();
        
        if ((d == -1) || (e == -1))
            return new XPolynomial();
        
        int n = d + e;
        
        XPolynomial result = new XPolynomial(n);
        
        for (int i = 0; i <= d; i++)
            for (int j = 0; j <= e; j++)
                result.coefficients[i+j] +=
                    (p.getCoefficient(i) * q.getCoefficient(j));
        
        return result;
    }
    
    
    /**
     * <p>Returns a polynomial array consisting of 2 items: the quotient
     * of p divided by q and the remainder of p divided by q.</p>
     *
     * <p>More precisely, if we let s,t denote the two components of the
     * returned array, then p = s * q + t and degree(t) &lt; degree(q).
     * The equality is correct to within roundoff error.</p>
     *
     * <p>If the polynomial q is <code>null</code> or the zero polynomial,
     * then throws an ArithmeticException with the message:</p>
     *
     * <p><code>Division by zero in class XPolynomial</code>.</p>
     *
     * <p>Otherwise, if the polynomial p is <code>null</code> or the zero
     * polynomial, returns a pair of zero polynomials.</p>
     *
     * @param p a polynomial
     * @param q a polynomial
     */
    public static XPolynomial[] divide(XPolynomial p, XPolynomial q) {
        if (q == null)
            throw new ArithmeticException(divisionMessage);
        
        int e = q.getDegree();
        
        if (e == -1)
            throw new ArithmeticException(divisionMessage);
        
        if (p == null)
            return new XPolynomial[]
                { new XPolynomial(), new XPolynomial() };
        
        int d = p.getDegree();
        
        if (d == -1)
            return new XPolynomial[]
                { new XPolynomial(), new XPolynomial() };
        
        int n = d - e;
        
        XPolynomial quo = new XPolynomial(n);
        XPolynomial rem = new XPolynomial(p);
        
        if (n >= 0) {
            double leading = q.coefficients[e];
            
            for (int i = n; i >= 0; i--) {
                double ratio = rem.coefficients[e + i] / leading;
                
                quo.coefficients[i] = ratio;
                rem.coefficients[e + i] = 0;
                
                for (int j = 0; j < e; j++)
                    rem.coefficients[j + i] -= (ratio * q.coefficients[j]);
            }
        }
        
        rem.shrinkCapacity();
        return new XPolynomial[] { quo, rem };
    }
    
    
    /////////////////////////////
    // Property Change Support //
    /////////////////////////////
    
    /**
     * Registers the given object 
     * to listen for property change events 
     * generated by this object.
     *
     * @param listener the listener to be registered
     */
    public void addPropertyChangeListener(
        PropertyChangeListener listener) 
    {
        changeAdapter.addPropertyChangeListener(listener);
    }
    
    
    /**
     * Registers the given object 
     * to listen for property change events
     * generated by this object
     * with the given property name.
     *
     * @param propertyName the name of the desired property 
     * @param listener the listener to be registered
     */
    public void addPropertyChangeListener(
        String propertyName,
        PropertyChangeListener listener) 
    {
        changeAdapter.addPropertyChangeListener(
            propertyName, 
            listener);
    }
    
    
    /**
     * Deregisters the given object 
     * from listening for property change events 
     * generated by this object.
     *
     * @param listener the listener to be deregistered
     */
    public void removePropertyChangeListener(
        PropertyChangeListener listener) 
    {
        changeAdapter.removePropertyChangeListener(listener);
    }
    
    
    /**
     * Deregisters the given object 
     * from listening for property change events
     * generated by this object
     * with the given property name.
     *
     * @param propertyName the name of the desired property 
     * @param listener the listener to be deregistered
     */
    public void removePropertyChangeListener(
        String propertyName,
        PropertyChangeListener listener) 
    {
        changeAdapter.removePropertyChangeListener(
            propertyName, 
            listener);
    }
    
}
