[Biojava-l] ChangeSupport.java

Peter Kharchenko pkharch@fas.harvard.edu
Mon, 04 Mar 2002 22:06:27 -0500


This is a multi-part message in MIME format.
--------------060508070904090205070502
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

Biojava guys (/girls?) ,

 I am using biojava for sequence alignment stats. I ran into a really 
bad performance problem, which was apparently was caused by a rather 
naive implementation of org.biojava.util.ChangeSupport class.  Rewriting 
that class sped up my application by more then factor of 10.
 I am sending you a copy of my implementation. I haven't changed any of 
the methods (although some constructors are rather meaningless now), so 
you should be able to just swap it in.

-peter.


--------------060508070904090205070502
Content-Type: text/plain;
 name="ChangeSupport.java"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="ChangeSupport.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.Serializable;
import java.util.*;
import java.lang.ref.*;

/**
 * A utility class to provide management for informing ChangeListeners of
 * ChangeEvents.
 * <P>
 * This is loosely modelled after the standard PropertyChangeEvent objects.
 * <P>
 * For an object to correctly fire these events, they must follow a broad
 * outline like this:
 * <code><pre>
 * public void mutator(foo arg) throw ChangeVetoException {
 *   ChangeEvent cevt = new ChangeEvent(this, SOME_EVENT_TYPE, arg);
 *   synchronized(changeSupport) {
 *     changeSupport.firePreChangeEvent(cevt);
 *     // update our state using arg
 *     // ...
 *     changeSupport.firePostChangeEvent(cevt);
 *   }
 * }
 * </pre></code>
 * The methods that delegate adding and removing listeners to a ChangeSupport
 * must take responsibility for synchronizing on the delegate.
 *
 * @author Matthew Pocock
 * @author Thomas Down
 * @author Keith James (docs)
 * @since 1.1
 */

public class ChangeSupport {
    private Hashtable changeTypes;

    /**
     * Generate a new ChangeSupport instance.
     */
    public ChangeSupport() {
	changeTypes=new Hashtable();
    }

    /**
     * Generate a new ChangeSupport instance which has room for initialSize
     * listeners before it needs to grow any resources.
     *
     * @param initialSize  the number of listeners that can be added before this
     *                     needs to grow for the first time
     */
    public ChangeSupport(int initialSize) {
        this();
    }

    /**
     * Generate a new ChangeSupport instance which has room for initialSize
     * listeners before it needs to grow any resources, and which will grow by
     * delta each time.
     *
     * @param initialSize  the number of listeners that can be added before this
     *                     needs to grow for the first time
     * @param delta  the number of listener slots that this will grow by each time
     *               it needs to
     */
    public ChangeSupport(int initialSize, int delta) {
	this();
    }

    /**
     * Add a listener that will be informed of all changes.
     *
     * @param cl  the ChangeListener to add
     */
    public void addChangeListener(ChangeListener cl) {
        addChangeListener(cl, ChangeType.UNKNOWN);
    }

    /**
     * Add a listener that will be informed of changes of a given type (and it's subtypes)
     *
     * @param cl  the ChangeListener
     * @param ct  the ChangeType it is to be informed of
     */
    public synchronized void addChangeListener(ChangeListener cl, ChangeType ct) {
        // Needed to synchronize this method in case multiple threads attempt to add a change listener at the same time.
        // Richard J. Fox  05/30/2001
        if (ct == null) {
            throw new NestedError("Since 1.2, listeners registered for the null changetype are not meaningful.  Please register a listener for ChangeType.UNKNOWN instead");
        } else {
            ChangeTypeListeners ctl=(ChangeTypeListeners)changeTypes.get(ct);
            // if this type has not been registered before
            if(ctl==null) {
		ctl=new ChangeTypeListeners(ct);
		changeTypes.put(ct,ctl);
            }
	}
    }

    /**
     * Remove a listener that was interested in all types of changes.
     *
     * @param cl  a ChangeListener to remove
     */
    public void removeChangeListener(ChangeListener cl) {
        removeChangeListener(cl, ChangeType.UNKNOWN);
    }

    /**
     * Remove a listener that was interested in a specific types of changes.
     *
     * @param cl  a ChangeListener to remove
     * @param ct  the ChangeType that it was interested in
     */
    public void removeChangeListener(ChangeListener cl, ChangeType ct) {
	if(ct==null) {
            throw new NestedError("Since 1.2, listeners registered for the null changetype are not meaningful.  Please register a listener for ChangeType.UNKNOWN instead");
	} else {
            ChangeTypeListeners ctl=(ChangeTypeListeners)changeTypes.get(ct);
            if(ct!=null) {
                ctl.listeners.remove(cl);
            }
	}
    }

    /**
     * Inform the listeners that a change is about to take place using their
     * firePreChangeEvent methods.
     * <P>
     * Listeners will be informed if they were interested in all types of event,
     * or if ce.getType() is equal to the type they are registered for.
     *
     * @param ce  the ChangeEvent to pass on
     * @throws ChangeVetoException if any of the listeners veto this change
     */
    public void firePreChangeEvent(ChangeEvent ce)
        throws ChangeVetoException {
        ChangeType ct = ce.getType();
        if(ct!=null) {
            ChangeTypeListeners ctl=(ChangeTypeListeners)changeTypes.get(ct);
            if(ctl!=null) {
		for(Iterator i=ctl.listeners.iterator();i.hasNext();) {
                    ChangeListener cl=(ChangeListener)i.next();
                    cl.preChange(ce);
		}
            }
        }
    }

    /**
     * Inform the listeners that a change has taken place using their
     * firePostChangeEvent methods.
     * <P>
     * Listeners will be informed if they were interested in all types of event,
     * or if ce.getType() is equal to the type they are registered for.
     *
     * @param ce  the ChangeEvent to pass on
     */

    public void firePostChangeEvent(ChangeEvent ce) {
        ChangeType ct = ce.getType();
        if(ct!=null) {
            ChangeTypeListeners ctl=(ChangeTypeListeners)changeTypes.get(ct);
            if(ctl!=null) {
		for(Iterator i=ctl.listeners.iterator();i.hasNext();) {
                    ChangeListener cl=(ChangeListener)i.next();
                    cl.postChange(ce);
		}
            }
        }
    }

    /** A little private class to hold a change type together with a set of listeners for that type */
    private class ChangeTypeListeners {
	Set listeners;
        ChangeType type;

        public ChangeTypeListeners(ChangeType t) {
            this.type=t;
            this.listeners=Collections.synchronizedSet(new HashSet());
        }
    }
}

--------------060508070904090205070502--