// // ActionImpl.java // /* 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; import java.rmi.RemoteException; import java.util.Enumeration; import java.util.Vector; import visad.util.ThreadPool; /* Action - ThingReference event logic ActionImpl has Vector of ReferenceActionLinks, one per linked ThingReference ThingReferenceImpl has Vector of ThingChangedLinks, one per linked Action call stacks: // create and send ThingChangedEvent ThingReferenceImpl.incTick() calls ThingChangedLink.queueThingChangedEvent('new' ThingChangedEvent e) calls Action.thingChanged(e) calls ReferenceActionLink.acknowledgeThingChangedEvent(e.getTick()) // get queued ThingChangedEvent ActionImpl.run() calls ReferenceActionLink.getThingChangedEvent() calls ThingReference.acknowledgeThingChanged(Action a) calls ThingChangedLink.acknowledgeThingChangedEvent() ActionImpl.thingChanged(ThingChangedEvent e) calls ReferenceActionLink.acknowledgeThingChangedEvent(e.getTick()) // peek at queued ThingChangedEvent ActionImpl.run() calls ReferenceActionLink.peekThingChangedEvent() calls ThingReference.peekThingChanged() calls ThingChangedLink.peekThingChangedEvent() */ /** * ActionImpl is the abstract superclass for runnable threads that * need to be notified when ThingReference objects change.<P> * * ActionImpl is the superclass of DisplayImpl and CellImpl.<P> * * ActionImpl is not Serializable and should not be copied * between JVMs.<P> */ public abstract class ActionImpl implements Action, Runnable { /** * Indicates whether we print out the trace from where an action is invoked and print its run time */ public static final boolean TRACE_TIME = Boolean.parseBoolean(System.getProperty("visad.actionimpl.tracetime", "false")); /** */ public static final boolean TRACE_STACK = Boolean.parseBoolean(System.getProperty("visad.actionimpl.tracestack", "false")); /** */ private String stackTrace; /** thread pool and its lock */ private transient static ThreadPool pool = null; /** */ private static Object poolLock = new Object(); /** */ private boolean enabled = true; /** */ private Object lockEnabled = new Object(); /** */ private boolean peek = false; /** */ private Thread currentActionThread = null; /** String name, used only for debugging */ private String Name; // WLH 17 Dec 2001 - get it off Thread stack /** */ private Enumeration run_links = null; /** * Vector of ReferenceActionLink-s; * ActionImpl is not Serializable, but mark as transient anyway */ private transient Vector LinkVector = new Vector(); /** * counter used to give a unique id to each ReferenceActionLink * in LinkVector */ private long link_id; /** */ private boolean requeue = false; /** * construct an ActionImpl * @param name - String name, used only for debugging */ public ActionImpl(String name) { // if the thread pool hasn't been initialized... if (pool == null) { startThreadPool(); } Name = name; link_id = 0; } /** used internally to create the shared Action thread pool */ private static void startThreadPool() { synchronized (poolLock) { if (pool == null) { // ...fill the pool; die if pool wasn't created try { pool = new ThreadPool("ActionThread"); } catch (Exception e) { System.err.println(e.getClass().getName() + ": " + e.getMessage()); System.exit(1); } } } } /** * return the number of tasks in the threadpool queue * @return number of queued and active tasks */ public static int getTaskCount() { if (pool == null) return 0; return pool.getTaskCount(); } /** * */ public static void printPool() { if (pool != null) { pool.printPool(); } } /** * destroy all threads after they've drained the job queue */ public static void stopThreadPool() { if (pool != null) { pool.stopThreads(); pool = null; } } /** * increase the maximum number of Threads allowed in the ThreadPool * @param num - new maximum number of Threads in ThreadPool * @throws Exception - num is less than previous maximum */ public static void setThreadPoolMaximum(int num) throws Exception { if (pool == null) { startThreadPool(); } pool.setThreadMaximum(num); } /** * stop activity in this ActionImpl */ public void stop() { if (LinkVector == null) return; synchronized (LinkVector) { Enumeration links = LinkVector.elements(); while(links.hasMoreElements()) { ReferenceActionLink link = (ReferenceActionLink)links.nextElement(); try { link.getThingReference().removeThingChangedListener( link.getAction()); } catch (RemoteException e) { } catch (VisADException e) { } } LinkVector.removeAllElements(); } // WLH 17 Dec 2001 if (pool != null && !pool.isTerminated()) { pool.queue(this); } run_links = null; } /** * @return long value of long counter used to give a unique id to * each linked ReferenceActionLink */ synchronized long getLinkId() { long i = link_id; link_id++; return i; } /** * call setTicks() for each linked ReferenceActionLink * which saves boolean flag indicating whether its incTick() * has been called since last setTicks() */ private void setTicks() { synchronized (LinkVector) { Enumeration links = LinkVector.elements(); while(links.hasMoreElements()) { ReferenceActionLink link = (ReferenceActionLink)links.nextElement(); link.setTicks(); } } } /** * @return boolean that is disjunction (or) of flags saved * in setTicks() calls to each linked ReferenceActionLink */ public boolean checkTicks() { boolean doIt = false; synchronized (LinkVector) { Enumeration links = LinkVector.elements(); while(links.hasMoreElements()) { ReferenceActionLink link = (ReferenceActionLink)links.nextElement(); doIt |= link.checkTicks(); } } return doIt; } /** * call resetTicks() for each linked ReferenceActionLink * which resets boolean flag indicating whether its incTick() * has been called since last setTicks() */ private void resetTicks() { synchronized (LinkVector) { Enumeration links = LinkVector.elements(); while(links.hasMoreElements()) { ReferenceActionLink link = (ReferenceActionLink)links.nextElement(); link.resetTicks(); } } } /** * enable activity in this ActionImpl and trigger any pending activity */ public void enableAction() { // System.out.println("enableAction " + getName()); if (!enabled) peek = true; enabled = true; notifyAction(); } /** * disable activity in this ActionImpl and if necessary wait * for end of current doAction() call */ public void disableAction() { // System.out.println("disableAction " + getName()); enabled = false; // wait for possible current run() invocation to finish synchronized (lockEnabled) { enabled = false; // probably not necessary, just don't trust a nop } } /** * Set the "enabled" state of this action. This may be used in code like the * following to ensure that the action has the same "enabled" state on leaving * the code as it did on entering it: * <BLOCKQUOTE> * <PRE><CODE> * ActionImpl action = ...; * boolean wasEnabled = action.setEnabled(false); * ... * action.setEnabled(wasEnabled); * </CODE></PRE> * </BLOCKQUOTE> * @param enable The new "enabled" state for this action. * @return The previous "enabled" state of this action. */ public boolean setEnabled(boolean enable) { boolean wasEnabled; synchronized (lockEnabled) { wasEnabled = enabled; if (enable && !wasEnabled) { enableAction(); } else if (!enable && wasEnabled) { disableAction(); } } return wasEnabled; } /** * return Thread currently active in run() method of this * ActionImpl, or null is run() is not active * * @return currently active thread or null */ public Thread getCurrentActionThread() { return currentActionThread; } /** * remove linked ReferenceActionLink * @param link - linked ReferenceActionLink to remove */ void handleRunDisconnectException(ReferenceActionLink link) { LinkVector.removeElement(link); } /** * invoked by a Thread from the ThreadPool whenever * there is a request for activity in this ActionImpl */ public void run() { // Save the current thread so we can prohibit it from calling // getImage. This is thread-safe, because only one ActionImpl // thread can be running at a time. currentActionThread = Thread.currentThread(); synchronized (lockEnabled) { // if (getName() != null) System.out.println("ENABLED = " + enabled + " " + getName()); if (enabled) { try { if (peek) { // WLH 17 Dec 2001 - keep run_links off Thread stack synchronized (LinkVector) { run_links = ((Vector)LinkVector.clone()).elements(); } while(run_links.hasMoreElements()) { ReferenceActionLink link = (ReferenceActionLink)run_links.nextElement(); try { link.peekThingChangedEvent(); } catch (RemoteException re) { if (!visad.collab.CollabUtil.isDisconnectException(re)) { throw re; } // remote side has died handleRunDisconnectException(link); } } run_links = null; peek = false; } // end if (peek) setTicks(); if (checkTicks()) { // if (getName() != null) System.out.println("RUN " + getName()); long t1 = System.currentTimeMillis(); doAction(); long t2 = System.currentTimeMillis(); //If it took longer than 10 milliseconds then do the trace if ((t2 - t1) > 10) { if (TRACE_TIME) { System.out.println( "Action:" + getClass().getName() + " time:" + (t2 - t1)); } if (TRACE_STACK) { String[] lines = stackTrace.split("\n"); for (int i = 0; i < lines.length && i < 30; i++) { if (i > 1) { System.out.println(lines[i]); } } } } } // WLH 17 Dec 2001 - keep run_links off Thread stack synchronized (LinkVector) { run_links = ((Vector)LinkVector.clone()).elements(); } while(run_links.hasMoreElements()) { ReferenceActionLink link = (ReferenceActionLink)run_links.nextElement(); ThingChangedEvent e; try { e = link.getThingChangedEvent(); } catch (RemoteException re) { if (!visad.collab.CollabUtil.isDisconnectException(re)) { throw re; } // remote side has died handleRunDisconnectException(link); e = null; } if (e != null) { thingChanged(e); } } run_links = null; resetTicks(); } catch (VisADException v) { v.printStackTrace(); throw new VisADError("Action.run: " + v.toString()); } catch (RemoteException v) { v.printStackTrace(); throw new VisADError("Action.run: " + v.toString()); } } // end if (enabled) // if there's more to do, add this to the end of the task list if (requeue) { if (pool != null) { // if (getName() != null) System.out.println("requeue " + getName()); pool.queue(this); } requeue = false; } } // end synchronized (lockEnabled) currentActionThread = null; } /** * abstract method that implements activity of this ActionImpl * @throws VisADException a VisAD error occurred * @throws RemoteException an RMI error occurred */ public abstract void doAction() throws VisADException, RemoteException; /** * a linked ThingReference has changed, requesting activity * in this ActionImpl * @param e ThingChangedEvent for change to ThingReference * * @return true if the ThingReference changed * @throws VisADException a VisAD error occurred * @throws RemoteException an RMI error occurred */ public boolean thingChanged(ThingChangedEvent e) throws VisADException, RemoteException { long id = e.getId(); ReferenceActionLink link = findLink(id); boolean changed = true; if (link != null) { link.acknowledgeThingChangedEvent(e.getTick()); notifyAction(); changed = false; } return changed; } /** * add a link to a ReferenceActionLink (and via it * link to a ThingReference) * @param link ReferenceActionLink to link to * @throws VisADException a VisAD error occurred * @throws RemoteException an RMI error occurred */ void addLink(ReferenceActionLink link) throws VisADException, RemoteException { ThingReference ref = link.getThingReference(); if (findReference(ref) != null) { throw new ReferenceException("Action.addLink: link to " + "ThingReference already exists"); } // WLH 4 Dec 98 - moved this above LinkVector stuff ref.addThingChangedListener(link.getAction(), link.getId()); if (LinkVector == null) LinkVector = new Vector(); synchronized (LinkVector) { LinkVector.addElement(link); } } /** * trigger activity in this ActionImpl */ void notifyAction() { // if (getName() != null) DisplayImpl.printStack("notifyAction " + getName()); requeue = true; if (pool == null) { startThreadPool(); } if (TRACE_STACK) { stackTrace = visad.util.Util.getStackTrace(); } pool.queue(this); } /** * wait for all queued tasks in ThreadPool to finish */ public void waitForTasks() { if (pool != null) { pool.waitForTasks(); } } /** * Creates a link to a ThingReference. Note that this method causes this * object to register itself with the ThingReference. * @param ref The ThingReference to which to create * the link. Subsequent invocation of * <code>thingChanged(ThingChangedEvent)</code> * causes invocation of * <code>ref.acknowledgeThingChanged(this)</code> * . This method invokes <code> * ref.addThingChangedListener(this, ...)</code>. * @throws RemoteVisADException if the reference isn't a {@link * ThingReferenceImpl}. * @throws ReferenceException if the reference has already been added. * @throws VisADException if a VisAD failure occurs. * @throws RemoteException if a Java RMI failure occurs. * @see #thingChanged(ThingChangedEvent) * @see ThingReference#addThingChangedListener(ThingChangedListener, long) */ public void addReference(ThingReference ref) throws ReferenceException, RemoteVisADException, VisADException, RemoteException { if (!(ref instanceof ThingReferenceImpl)) { throw new RemoteVisADException("ActionImpl.addReference: requires " + "ThingReferenceImpl"); } if (findReference(ref) != null) { throw new ReferenceException("ActionImpl.addReference: " + "link already exists"); } addLink(new ReferenceActionLink(ref, this, this, getLinkId())); notifyAction(); } /** * does essentially the same thing as addReference(), but is * called by the addReference() method of any RemoteActionImpl * that adapts this ActionImpl * @param ref RemoteThingReference being linked * @param action RemoteActionImpl adapting this ActionImpl * @throws ReferenceException if the reference has already been added. * @throws VisADException if a VisAD failure occurs. * @throws RemoteException if a Java RMI failure occurs. */ void adaptedAddReference(RemoteThingReference ref, Action action) throws VisADException, RemoteException { if (findReference(ref) != null) { throw new ReferenceException("ActionImpl.adaptedAddReference: " + "link already exists"); } addLink(new ReferenceActionLink(ref, this, action, getLinkId())); notifyAction(); } /** * <p>Removes a link to a ThingReference.</p> * * <p>This implementation invokes {@link #findReference(ThingReference)}.</p> * * @param ref The reference to be removed. * @throws RemoteVisADException if the reference isn't a {@link * ThingReferenceImpl}. * @throws ReferenceException if the reference isn't a part of this * instance. * @throws VisADException if a VisAD failure occurs. * @throws RemoteException if a Java RMI failure occurs. */ public void removeReference(ThingReference ref) throws VisADException, RemoteException { ReferenceActionLink link = null; if (!(ref instanceof ThingReferenceImpl)) { throw new RemoteVisADException("ActionImpl.removeReference: requires " + "ThingReferenceImpl"); } if (LinkVector != null) { synchronized (LinkVector) { link = findReference(ref); if (link == null) { throw new ReferenceException("ActionImpl.removeReference: " + "ThingReference not linked"); } LinkVector.removeElement(link); } } if (link != null) ref.removeThingChangedListener(link.getAction()); notifyAction(); } /** * does essentially the same thing as removeReference(), but is * called by the removeReference() method of any RemoteActionImpl * that adapts this ActionImpl * @param ref RemoteThingReference being removed * @throws ReferenceException if the reference is not linked. * @throws VisADException if a VisAD failure occurs. * @throws RemoteException if a Java RMI failure occurs. */ void adaptedRemoveReference(RemoteThingReference ref) throws VisADException, RemoteException { ReferenceActionLink link = null; if (LinkVector != null) { synchronized (LinkVector) { link = findReference(ref); if (link == null) { throw new ReferenceException("ActionImpl.adaptedRemoveReference: " + "ThingReference not linked"); } LinkVector.removeElement(link); } } if (link != null) ref.removeThingChangedListener(link.getAction()); notifyAction(); } /** * delete all links to ThingReferences * * @throws RemoteException * @throws VisADException */ public void removeAllReferences() throws VisADException, RemoteException { Vector cloneLink = null; if (LinkVector != null) { synchronized (LinkVector) { cloneLink = (Vector)LinkVector.clone(); LinkVector.removeAllElements(); } } if (cloneLink != null) { Enumeration links = cloneLink.elements(); while(links.hasMoreElements()) { ReferenceActionLink link = (ReferenceActionLink)links.nextElement(); ThingReference ref = link.getThingReference(); ref.removeThingChangedListener(link.getAction()); } } notifyAction(); } /** * called by DisplayImpl.removeReference and * DisplayImpl.adaptedDisplayRemoveReference to remove links * @param links array of ReferenceActionLinks to remove * @throws VisADException if a VisAD failure occurs. * @throws RemoteException if a Java RMI failure occurs. */ void removeLinks(ReferenceActionLink[] links) throws VisADException, RemoteException { if (LinkVector != null) { synchronized (LinkVector) { for (int i = 0; i < links.length; i++) { if (!LinkVector.removeElement(links[i])) links[i] = null; } } } for (int i = 0; i < links.length; i++) { if (links[i] != null) { ThingReference ref = links[i].getThingReference(); try { ref.removeThingChangedListener(links[i].getAction()); } catch (RemoteException re) { // don't throw exception if the other side has died if (!visad.collab.CollabUtil.isDisconnectException(re)) { throw re; } } } } notifyAction(); } /** * returns a linked ReferenceActionLink with the given id * @param id value to search for * @return linked ReferenceActionLink with given id * @throws VisADException if a VisAD failure occurs. */ ReferenceActionLink findLink(long id) throws VisADException { if (LinkVector == null) return null; synchronized (LinkVector) { Enumeration links = LinkVector.elements(); while(links.hasMoreElements()) { ReferenceActionLink link = (ReferenceActionLink)links.nextElement(); if (id == link.getId()) return link; } } return null; } /** * Returns the link associated with a ThingReference. * * @param ref The reference to find. * @return The link associated with the reference. * @throws ReferenceException if the argument is <code>null</code>. * @throws VisADException if the argument is <code>null</code>. */ public ReferenceActionLink findReference(ThingReference ref) throws VisADException { if (ref == null) { throw new ReferenceException("ActionImpl.findReference: " + "ThingReference cannot be null"); } if (LinkVector == null) return null; synchronized (LinkVector) { Enumeration links = LinkVector.elements(); while(links.hasMoreElements()) { ReferenceActionLink link = (ReferenceActionLink)links.nextElement(); if (ref.equals(link.getThingReference())) return link; } } return null; } /** * @return Vector of linked ReferenceActionLinks */ public Vector getLinks() { return (Vector)LinkVector.clone(); } /** * @return String name of this Action */ public String getName() { return Name; } /** * change the name of this Action * @param name new String name */ public void setName(String name) { Name = name; } }