/* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad.collab; import java.rmi.RemoteException; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.ListIterator; import java.util.Vector; import visad.ConstantMap; import visad.Control; import visad.DataRenderer; import visad.DisplayImpl; import visad.RemoteDataReference; import visad.RemoteDisplayImpl; import visad.RemoteReferenceLink; import visad.RemoteVisADException; import visad.ScalarMap; import visad.VisADException; public class DisplaySyncImpl implements Comparator, DisplaySync, Runnable { private String Name; private DisplayImpl myDisplay; private DisplayMonitor monitor; private Object mapClearSync = new Object(); private int mapClearCount = 0; private boolean dead = false; private Object tableLock = new Object(); private Thread thisThread = null; private HashMap current = new HashMap(); private HashMap diverted = null; public DisplaySyncImpl(DisplayImpl dpy) throws RemoteException { Name = dpy.getName() + ":Sync"; myDisplay = dpy; monitor = dpy.getDisplayMonitor(); monitor.setDisplaySync(this); } /** * Adds the specified data reference to this <TT>Display</TT>. * * @param link The link to the remote data reference. * * @exception VisADException If a link could not be made for the * remote data reference. */ private void addLink(RemoteReferenceLink link) throws VisADException { // build array of ConstantMap values ConstantMap[] cm = null; try { Vector v = link.getConstantMapVector(); int len = v.size(); if (len > 0) { cm = new ConstantMap[len]; for (int i = 0; i < len; i++) { ConstantMap tmp = (ConstantMap )v.elementAt(i); cm[i] = (ConstantMap )tmp.clone(); } } } catch (Exception e) { throw new VisADException("Couldn't copy ConstantMaps" + " for remote DataReference"); } // get reference to Data object RemoteDataReference ref; try { ref = link.getReference(); } catch (Exception e) { throw new VisADException("Couldn't copy remote DataReference"); } if (ref != null) { DataRenderer dr = myDisplay.getDisplayRenderer().makeDefaultRenderer(); String defaultClass = dr.getClass().getName(); // get proper DataRenderer DataRenderer renderer; try { String newClass = link.getRendererClassName(); if (newClass == defaultClass) { renderer = null; } else { Object obj = Class.forName(newClass).newInstance(); renderer = (DataRenderer )obj; } } catch (Exception e) { throw new VisADException("Couldn't copy remote DataRenderer " + "name; using " + defaultClass); } // build RemoteDisplayImpl to which reference is attached try { RemoteDisplayImpl rd = new RemoteDisplayImpl(myDisplay); // if this reference uses the default renderer... if (renderer == null) { rd.addReference(ref, cm); } else { rd.addReferences(renderer, ref, cm); } } catch (Exception e) { e.printStackTrace(); throw new VisADException("Couldn't add remote DataReference " + ref + ": " + e.getClass().getName() + ": " + e.getMessage()); } } } public int compare(Object o1, Object o2) { return (((MonitorEvent )o1).getSequenceNumber() - ((MonitorEvent )o2).getSequenceNumber()); } public void destroy() { monitor = null; myDisplay = null; } /** * Finds the first map associated with this <TT>Display</TT> * which matches the specified <TT>ScalarMap</TT>. * * @param map The <TT>ScalarMap</TT> to find. */ private ScalarMap findMap(ScalarMap map) { ScalarMap found = null; boolean isConstMap; Vector v; if (map instanceof ConstantMap) { v = myDisplay.getConstantMapVector(); isConstMap = true; } else { v = myDisplay.getMapVector(); isConstMap = false; } ListIterator iter = v.listIterator(); while (iter.hasNext()) { ScalarMap sm = (ScalarMap )iter.next(); if (sm.equals(map)) { found = sm; break; } } return found; } /** * Start event callback. */ public void eventReady(RemoteEventProvider provider, Object key) { synchronized (tableLock) { if (thisThread != null) { if (diverted == null) { diverted = new HashMap(); } diverted.put(key, provider); } else { current.put(key, provider); thisThread = new Thread(this); thisThread.start(); } } } public String getName() { return Name; } public boolean isLocalClear() { boolean result = true; synchronized (mapClearSync) { if (mapClearCount > 0) { mapClearCount--; result = false; } } return result; } public boolean isThreadRunning() { return (thisThread != null); } private void processMap(HashMap map) { MonitorEvent[] list = new MonitorEvent[map.size()]; // build the array of events Iterator iter = map.keySet().iterator(); for (int i = list.length - 1; i >= 0; i--) { if (iter.hasNext()) { String key = (String )iter.next(); list[i] = (MonitorEvent )map.get(key); } else { list[i] = null; } } // sort events by order of creation Arrays.sort(list, this); int i, attempts; i = attempts = 0; while (i < list.length) { try { processOneEvent(list[i]); i++; } catch (RemoteException re) { if (attempts++ < 5) { // wait a bit, then try again to request the events try { Thread.sleep(500); } catch (InterruptedException ie) { } } else { // if we failed to connect for 10 times, give up dead = true; break; } } catch (RemoteVisADException rve) { System.err.println("While processing " + list[i] + ":"); i++; rve.printStackTrace(); } } } private void processOneEvent(MonitorEvent evt) throws RemoteException, RemoteVisADException { Control lclCtl, rmtCtl; ScalarMap lclMap, rmtMap; switch (evt.getType()) { case MonitorEvent.MAP_ADDED: rmtMap = ((MapMonitorEvent )evt).getMap(); // if we haven't already added this map... if (findMap(rmtMap) == null) { /* WLH 26 Dec 2002 if (!myDisplay.getRendererVector().isEmpty()) { System.err.println("Late addMap: " + rmtMap); } else { */ try { myDisplay.addMap(rmtMap, evt.getOriginator()); } catch (VisADException ve) { ve.printStackTrace(); throw new RemoteVisADException("Map " + rmtMap + " not added: " + ve); } /* } */ } break; case MonitorEvent.MAP_REMOVED: rmtMap = ((MapMonitorEvent )evt).getMap(); // if we have already added this map... if (findMap(rmtMap) != null) { try { myDisplay.removeMap(rmtMap, evt.getOriginator()); } catch (VisADException ve) { ve.printStackTrace(); throw new RemoteVisADException("Map " + rmtMap + " not removed: " + ve); } } break; case MonitorEvent.MAP_CHANGED: rmtMap = ((MapMonitorEvent )evt).getMap(); lclMap = findMap(rmtMap); if (lclMap == null) { throw new RemoteVisADException("ScalarMap " + rmtMap + " not found"); } // CTR 2 June 2000 - do not set map range if already set (avoid loops) double[] rng = rmtMap.getRange(); double[] lclRng = lclMap.getRange(); if (rng[0] != lclRng[0] || rng[1] != lclRng[1]) { try { lclMap.setRange(rng[0], rng[1], evt.getOriginator()); } catch (VisADException ve) { throw new RemoteVisADException("Map not changed: " + ve); } } break; case MonitorEvent.MAPS_CLEARED: try { myDisplay.removeAllReferences(); myDisplay.clearMaps(); } catch (VisADException ve) { throw new RemoteVisADException("Maps not cleared: " + ve); } catch (NullPointerException npe) { npe.printStackTrace(); throw new RemoteVisADException("Maps not cleared"); } break; case MonitorEvent.REFERENCE_ADDED: RemoteReferenceLink ref = ((ReferenceMonitorEvent )evt).getLink(); try { addLink(ref); } catch (VisADException ve) { throw new RemoteVisADException("DataReference " + ref + " not found by " + Name + ": " + ve.getMessage()); } break; case MonitorEvent.CONTROL_INIT_REQUESTED: // !!! DON'T FORWARD INIT EVENTS TO LISTENERS !!! rmtCtl = ((ControlMonitorEvent )evt).getControl(); lclCtl = myDisplay.getControl(rmtCtl.getClass(), rmtCtl.getInstanceNumber()); if (lclCtl == null) { // didn't find control ... maybe it doesn't exist yet? break; } try { ControlMonitorEvent cme; cme = new ControlMonitorEvent(MonitorEvent.CONTROL_CHANGED, (Control )lclCtl.clone()); monitor.notifyListeners(cme); } catch (VisADException ve) { throw new RemoteVisADException("Control " + rmtCtl + " not changed by " + Name + ": " + ve); } break; case MonitorEvent.CONTROL_CHANGED: rmtCtl = ((ControlMonitorEvent )evt).getControl(); lclCtl = myDisplay.getControl(rmtCtl.getClass(), rmtCtl.getInstanceNumber()); // skip this if we have change events to deliver for this control if (lclCtl != null && !monitor.hasEventQueued(evt.getOriginator(), lclCtl)) { try { lclCtl.syncControl(rmtCtl); } catch (VisADException ve) { throw new RemoteVisADException("Control " + lclCtl + " not changed by " + Name + ": " + ve.getMessage()); } } break; case MonitorEvent.MESSAGE_SENT: myDisplay.sendMessage(((MessageMonitorEvent )evt).getMessage()); break; default: throw new RemoteVisADException("Event " + evt + " not handled"); } } private HashMap requestEventTable(HashMap table) throws RemoteException { HashMap map = null; Iterator iter = table.keySet().iterator(); while (iter.hasNext()) { String key = (String )iter.next(); RemoteEventProvider provider = (RemoteEventProvider )table.get(key); iter.remove(); MonitorEvent evt = requestOneEvent(key, provider); if (evt != null) { if (map == null) { map = new HashMap(); } map.put(key, evt); } } return map; } private MonitorEvent requestOneEvent(String key, RemoteEventProvider provider) throws RemoteException { // get the event MonitorEvent evt; try { evt = provider.getEvent(key); } catch (RemoteVisADException rve) { rve.printStackTrace(); throw new RemoteException(rve.getMessage()); } if (evt == null) { // if it's already been picked up, we're done return null; } switch (evt.getType()) { case MonitorEvent.MAPS_CLEARED: synchronized (mapClearSync) { mapClearCount++; } break; case MonitorEvent.CONTROL_CHANGED: boolean result; try { result = monitor.hasEventQueued(evt.getOriginator(), ((ControlMonitorEvent )evt).getControl()); } catch (RemoteException re) { re.printStackTrace(); result = false; } if (result) { // drop this event since we're about to override it return null; } break; } return evt; } /** * Requests events from the remote provider(s). */ public void run() { HashMap map = null; boolean done = false; try { int attempts = 0; while (!done) { HashMap newMap; try { newMap = requestEventTable(current); done = true; } catch (RemoteException re) { if (attempts++ < 5) { // wait a bit, then try again to request the events try { Thread.sleep(500); } catch (InterruptedException ie) { } newMap = null; } else { // if we failed to connect for 10 times, give up dead = true; break; } } if (map == null) { map = newMap; } else if (newMap != null) { map.putAll(newMap); } if (done) { synchronized (tableLock) { if (!undivertEvents()) { break; } done = false; } } } } finally { if (map != null) { processMap(map); } // indicate that the thread has exited synchronized (tableLock) { thisThread = null; } } } /** * Returns <TT>true</TT> if there were diverted requests. */ private boolean undivertEvents() { final boolean undivert; synchronized (tableLock) { // if there are events queued, restore them to the main table undivert = (diverted != null); if (undivert) { current = diverted; diverted = null; } } return undivert; } }