// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/event/ProjectionSupport.java,v $
// $RCSfile: ProjectionSupport.java,v $
// $Revision: 1.10 $
// $Date: 2009/02/05 18:46:11 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.event;
import java.util.ListIterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.bbn.openmap.proj.Projection;
/**
* This is a utility class that can be used by beans that need support for
* handling ProjectionListeners and firing ProjectionEvents. You can use an
* instance of this class as a member field of your bean and delegate work to
* it.
*/
public class ProjectionSupport extends ListenerSupport<ProjectionListener> {
static Logger logger = Logger.getLogger("com.bbn.openmap.event.ProjectionSupport");
private static final long serialVersionUID = 1L;
protected ProjectionChangeNotifier pcNotifier;
protected boolean useNotifier;
/**
* Construct a ProjectionSupport.
*/
public ProjectionSupport(boolean useNotifier) {
this(null, useNotifier);
}
/**
* Construct a ProjectionSupport.
*
* @param aSource source Object
*/
public ProjectionSupport(Object aSource, boolean useNotifier) {
super(aSource);
this.useNotifier = useNotifier;
}
/**
* Send a center event to all registered listeners.
*
* @param proj Projection
*/
public void fireProjectionChanged(Projection proj) {
if (proj == null || isEmpty())
return; // no event or no listeners
if (useNotifier && pcNotifier == null) {
pcNotifier = new ProjectionChangeNotifier();
pcNotifier.start();
}
ProjectionEvent event = new ProjectionEvent(getSource(), proj);
if (pcNotifier != null) {
pcNotifier.fireProjectionEvent(event);
} else {
for (ProjectionListener listener : this) {
listener.projectionChanged(event);
}
}
}
/**
* Call when getting rid of the ProjectionSupport, it kills the
* ProjectionSupport thread. (from CJS)
*/
public void dispose() {
super.clear();
if (pcNotifier != null) {
pcNotifier.setTerminated(true);
pcNotifier.fireProjectionEvent(null);
pcNotifier.interrupt();
pcNotifier = null;
}
}
/**
* A thread that disperses the projection event, instead of letting the
* Swing thread do it. A new one is created for every projection change, so
* the current ProjectionEvent object is getting delivered with it.
*/
protected class ProjectionChangeNotifier extends Thread {
private final Object lock = new Object();
/* current projection event */
protected ProjectionEvent projEvent;
/* next projection event */
protected ProjectionEvent nextEvent;
/* a flag to know if we are terminated. */
protected boolean terminated = false;
public ProjectionChangeNotifier() {
setName("ProjectionSupportThread " + getName());
}
public boolean isTerminated() {
return terminated;
}
public void setTerminated(boolean terminated) {
this.terminated = terminated;
}
protected boolean isEventInProgress() {
// synchronized(lock){
return projEvent != null;
// }
}
public void fireProjectionEvent(ProjectionEvent event) {
synchronized (lock) {
nextEvent = event;
lock.notifyAll(); // wakes up thread if sleeping
}
}
public void run() {
while (!terminated) { // run while parent mapbean exists
synchronized (lock) {
if (nextEvent != null) {
projEvent = nextEvent;
nextEvent = null;
}
}
if (projEvent != null && !isEmpty()) {
// Instead of going top of map to bottom, go bottom to top:
/*
* Use this try/catch to deal with any problems getting
* clone of listeners, in case listener list is being
* changed while clone is being made, etc.
*/
try {
ListIterator<ProjectionListener> li = ProjectionSupport.this.listIterator();
while (li.hasPrevious()) {
ProjectionListener listener = li.previous();
if (nextEvent != null) {
break; // new event has been posted, bail out
}
/*
* Use this try/catch to eliminate problems from
* individual layers - just blow them off.
*/
try {
synchronized (lock) {
listener.projectionChanged(projEvent);
}
} catch (Exception e) {
if (logger.isLoggable(Level.FINE)) {
logger.info("ProjectionListener not handling projection well: "
+ listener.getClass().getName() + " : "
+ e.getClass().getName() + " : " + e.getMessage());
e.printStackTrace();
}
}
}
} catch (Exception e) {
logger.fine("caught exception: " + e.getClass().getName() + " : "
+ e.getMessage());
}
// notification is complete
synchronized (lock) {
projEvent = null;
}
} else { // there is no event
// just wait until we are awakened for the next event
try {
synchronized (lock) {
while (nextEvent == null) {
lock.wait();
}
}
} catch (InterruptedException x) {
// do nothing, just reenter loop
}
}
}
logger.fine("Projection notifier thread " + getName() + " done running");
}
}
}