[Biojava-l] Change events framework.

Thomas Down td2@sanger.ac.uk
Thu, 10 Aug 2000 19:22:13 +0100


--y0ulUmNC+osPPQO6
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hi...

Since we're heading towards many more mutable objects in BioJava
1.1, Matthew and I have been discussing a framework for notifying
interested parties of changes in BioJava objects, and allowing
unacceptable changes to be vetoed.

We hope to see a situation where any `mutability' method will
throw a ChangeVetoException if some object is listening to changes,
and objects.  Since this will be a sub-class of BioException,
client code doesn't have to differentiate between ChangeVetoExceptions
and other exceptions which are thrown if a method is called with
invalid arguments unless they want to do so.

Note that this isn't quite the same as Sun's PropertyChangeEvent
framework (although it should be easy to add some bridge code
to allow them to inter-operate).  The main issue with the 
java.beans events are that they assume that every change
is a property change.  In reality, many changes (adding or
removing a Feature from a Sequence, for instance) don't fit
into this model at all well.

I've attached drafts of the four main classes which make up
this interface (There will also be a ChangeSupport class, which
maintains a list of ChangeListeners for you).  One other
change which is likely to occur at the same time is to move
BioException into org.biojava.utils.  This will require most
programs to be recompiled when moving from 1.0 to 1.1 releases,
but does make the structure rather more logical.

Opinions?  Complaints?

Otherwise, I'll check this in in a day or two, and we can start
working mutability support into BioJava in earnest.

Thomas.

--y0ulUmNC+osPPQO6
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="ChangeEvent.java"

/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */

package org.biojava.utils;

import java.util.*;

/**
 * Event which encapsulates a change in any mutable BioJava object.
 *
 * @since 1.1
 */

public class ChangeEvent extends EventObject {
    private final ChangeType type;
    private final Object change;
    private final Object previous;
    private final ChangeEvent chain;

    /**
     * Construct a ChangeEvent with no change details.
     *
     * @param source The object being changed.
     * @param type The type of change being made.
     */

    public ChangeEvent(Object source, ChangeType type) {
	super(source);
	this.type = type;
    }

    /**
     * Construct a ChangeEvent specifying a new value for
     * a property, or an object to be added to a collection.
     *
     * @param source The object being changed.
     * @param type The type of change being made.
     * @param change The new value of the property being changed.
     */

    public ChangeEvent(Object source,
		       ChangeType type,
		       Object change)
    {
	super(source);
	this.type = type;
	this.change = change;
    }

    /**
     * Construct a ChangeEvent specifying a new value for
     * a property, and giving the previous value.
     *
     * @param source The object being changed.
     * @param type The type of change being made.
     * @param change The new value of the property being changed.
     * @param previous The old value of the property being changed.
     */

    public ChangeEvent(Object source,
		       ChangeType type,
		       Object change,
		       Object previous)
    {
	super(source);
	this.type = type;
	this.change = change;
	this.previous = previous;
    }

    /**
     * Construct a ChangeEvent to be fired because another ChangeEvent has
     * been received from a property object.
     *
     * @param source The object being changed.
     * @param type The type of change being made.
     * @param change The new value of the property being changed.
     * @param previous The old value of the property being changed.
     * @param chain The event which caused this event to be fired.
     */

    public ChangeEvent(Object source,
		       ChangeType type,
		       Object change,
		       Object previous
		       ChangeEvent chain)
    {
	super(source);
	this.type = type;
	this.change = change;
	this.previous = previous;
	this.chain = chain;
    }

    /**
     * Find the type of this event.
     */

    public ChangeType getType() {
	return type;
    }

    /**
     * Return an object which is to be the new value of some property,
     * or is to be added to a collection.  May return <code>null</code>
     * is this is not meaningful.
     */

    public Object getChange() {
	return change;
    }

    /**
     * Return the old value of a property being changed.  May return
     * <code>null</code> is this is not meaningful.
     */

    public Object getPrevious() {
	return previous;
    }

    /**
     * Return the event which caused this to be fired, or <code>null</code>
     * if this change was not caused by another event.
     */

    public ChangeEvent getChainedEvent() {
	return chain;
    }
}

--y0ulUmNC+osPPQO6
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="ChangeType.java"

/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */

package org.biojava.utils;

import java.io.*;

/**
 * Class for all constants which are used to indicate change
 * types.  Note that all ChangeType objects must be accessible
 * via a public static field of some class or interface.  These should
 * be specified at construction time, so that the ChangeType can
 * be properly serialized.  Typically, they should be constructed
 * using code like:
 *
 * <pre>
 * class MyClassWhichCanThrowChangeEvents {
 *     public final static ChangeEvent CHANGE_COLOR = new ChangeEvent(
 *             "Color change", 
 *	       MyClassWhichCanThrowChangeEvents.class,
 *             "CHANGE_COLOR");
 *
 *     // Rest of the class here...
 * }
 *
 * @since 1.1
 */

public final class ChangeType implements Serializable {
    /**
     * Constant ChangeType field which indicates that a change has
     * occured which can't otherwise be represented.  Please do not
     * use this when there is another, more sensible, option.
     */

    public final static UNKNOWN;

    static {
	UNKNOWN = new ChangeType("Unknown change",
	                         ChangeType.class,
				 "UNKNOWN");
    }

    private final String name;
    private final Field ourField;

    /**
     * Construct a new ChangeType.
     *
     * @param name The name of this change.
     * @param ourField The public static field which contains this ChangeType.
     */

    public ChangeType(String name, Field ourField) {
	this.name = name;
	this.ourField = ourField;
    }

    /**
     * Construct a new ChangeType.
     *
     * @param name The name of this change.
     * @param clazz The class which is going to contain this change.
     * @param fname The name of the field in <code>clazz</code> which
     *              is to contain a reference to this change.
     * @throws BioError If the field cannot be found.
     */

    public ChangeType(String name, Class clazz, String fname) 
    {
	this.name = name;
	try {
	    this.ourField = clazz.getField(fname);
	} catch (Exception ex) {
	    throw new BioError("Couldn't find field " + fname + " in class " + clazz.getName());
	}
    }

    /**
     * Return the name of this change.
     */

    public String getName() {
	return name;
    }

    /**
     * Return a string representation of this change.
     */

    public String toString() {
	return "ChangeType: " + name;
    }

    /**
     * Make a placeholder for this object in a serialized stream.
     */

    private Object writeReplace() {
	return new StaticMemberPlaceHolder(ourField);
    }
}

--y0ulUmNC+osPPQO6
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="ChangeListener.java"

/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */

package org.biojava.utils;

import java.util.*;

/**
 * Interface for objects which listen to ChangeEvents.
 *
 * @since 1.1
 */

public interface ChangeListener extends EventListener {
    /**
     * Conventience implementation which vetoes every change of which it is
     * notified.
     */

    public final static ChangeListener ALWAYS_VETO;

    static {
	ALWAYS_VETO = new AlwaysVetoListener();
    }

    /**
     * Called before a change takes place.
     *
     * @param cev An event encapsulting the change which is about
     *            to take place.
     * @throws ChangeVetoException if the receiver does not wish
     *                             this change to occur at this
     *                             time.
     */

    public void preChange(ChangeEvent cev) throws ChangeVetoException;

    /**
     * Called when a change has just taken place.
     *
     * @param cev An event encapulating the change which has
     *            occured.
     */

    public void postChange(ChangeEvent cev);

    /**
     * Class of ChangeListener.ALWAYS_VETO.
     *
     * @since 1.1
     */

    static class AlwaysVetoListener implements ChangeListener {
	private AlwaysVetoListener() {
	}

	public void preChange(ChangeEvent cev) throws ChangeVetoException {
	    throw new ChangeVetoException(cev);
	}
	
	public void postChange(ChangeEvent cev) {
	}
    }
}


--y0ulUmNC+osPPQO6
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="ChangeVetoException.java"

/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */

package org.biojava.utils;

/**
 * Exception which is thrown when a ChangeListener does not
 * wish a change to take place.
 *
 * @since 1.1
 */

public class ChangeVetoException extends BioException {
    private final ChangeEvent change;

    /**
     * Construct an exception to veto a change without explanation.
     * 
     * @param change An event which is being vetoed.
     */

    public ChangeVetoException(ChangeEvent change) {
	super();
	this.change = change;
    }

    /**
     * Construct an exception to veto a change for a specified reason.
     * 
     * @param change An event which is being vetoed.
     * @param reason A detail message.
     */

    public ChangeVetoException(ChangeEvent change, String reason) {
	super(reason);
	this.change = change;
    }

    /**
     * Propogate an exception without (additional) explanation.
     * 
     * @param ex A parent exception
     * @param change An event which is being vetoed.
     */

    public ChangeVetoException(Exception ex, ChangeEvent change) {
	super(ex);
	this.change = change;
    }

    /**
     * Propogate an exception, giving a detail message
     * 
     * @param ex A parent exception
     * @param change An event which is being vetoed.
     * @param reason A detail message.
     */

    public ChangeVetoException(Exception ex,
			       ChangeEvent change,
			       String reason) 
    {
	super(ex, reason);
	this.change = change;
    }

    /**
     * Return the ChangeEvent which is being vetoed.
     */

    public ChangeEvent getChangeEvent() {
	return change;
    }
}

--y0ulUmNC+osPPQO6--