import java.util.ArrayList; import java.util.Iterator; import org.apache.log4j.Logger; import com.bbn.openmap.MapBean; import com.bbn.openmap.event.ProjectionEvent; import com.bbn.openmap.event.ProjectionListener; import com.bbn.openmap.event.ProjectionSupport; import com.bbn.openmap.proj.Projection; /** * Creates a thread that will distribute ProjectionEvents and call listeners' * projectionChanged() methods. * After it is first started, the thread never terminates. */ public class ProjectionSupportWithThread extends ProjectionSupport { /** The Logger for this class */ private static Logger logger = Logger.getLogger( ProjectionSupportWithThread.class.getName() ); /** * This thread will distribute the ProjectionEvents and call listeners' * projectionChanged() methods. */ protected Thread t = null; /** The event queue */ protected ArrayList events = new ArrayList(); //protected int MAX_QUEUE_SIZE = 10; //how many projection events before dropping some /** * Construct a ProjectionSupport. * * @param aSource The MapBean that's constructing this object. */ public ProjectionSupportWithThread ( MapBean aSource ) { super( aSource ); t = new Thread( new ProjectionChangedRunnable() ); t.setName( aSource.getClass().getSimpleName() + "-Projection-Thread" ); t.start(); } /** * A Runnable class that distributes the projection. */ protected class ProjectionChangedRunnable implements Runnable { public void run () { /** If new projection is the same as the previous, it will be ignored */ Projection prevProj = null; while ( true ) { /* Wait for a projection event to come in */ ProjectionEvent projEvent = null; synchronized ( events ) { while ( events.isEmpty() ) { try { events.wait(); } catch ( InterruptedException e ) { logger.debug( "Error - ProjectionChangeRunnable: interrupted: " + e ); } } projEvent = events.remove( 0 ); } Thread.yield(); // Yield to (hopefully) the AWT-event thread so it can take care of any pending repaints. notifyListeners( projEvent ); /* Whether or not to repaint the map using invokeAndWait() */ Projection currProj = projEvent.getProjection(); if ( prevProj != null && ( currProj.getScale() != prevProj.getScale()) ) { try { ( (MyMapBean) getSource() ).repaintSynchronously(); // on zoom, } catch ( InterruptedException e ) { e.printStackTrace(); } } else { ( (MyMapBean) getSource() ).repaint(); // on pan } prevProj = currProj; } } /** * Utility method for notifying listeners. * * @param projEvent */ protected void notifyListeners ( ProjectionEvent projEvent ) { /* Send the event to all listeners */ Iterator listeners = iterator(); while ( listeners.hasNext() ) { ProjectionListener target = (ProjectionListener)listeners.next(); //logger.debug( "ProjectionChangeRunnable: firing projection change, target is: " + target ); try { target.projectionChanged( projEvent ); } catch ( Exception e ) { logger.error( "Projection Thread caught an unexpected exception when notifying " + target.getClass().getSimpleName() ); e.printStackTrace(); } } } } /** * Send a ProjectionEvent to all registered listeners. * * @param proj Projection */ public void fireProjectionChanged ( Projection proj ) { if ( size() == 0 ) return; /* queue up the event */ ProjectionEvent evt = new ProjectionEvent( getSource(), proj.makeClone() ); //NOTE: makeClone() is necessary to prevent proj internals from changing while it is being dispatched. synchronized ( events ) { if ( events.size() == 0 ) { events.add( evt ); } else { events.set( 0, evt ); } events.notify(); } } }