1// BEGIN LICENSE BLOCK
2// Version: CMPL 1.1
3//
4// The contents of this file are subject to the Cisco-style Mozilla Public
5// License Version 1.1 (the "License"); you may not use this file except
6// in compliance with the License.  You may obtain a copy of the License
7// at www.eclipse-clp.org/license.
8//
9// Software distributed under the License is distributed on an "AS IS"
10// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
11// the License for the specific language governing rights and limitations
12// under the License.
13//
14// The Original Code is  The ECLiPSe Constraint Logic Programming System.
15// The Initial Developer of the Original Code is  Cisco Systems, Inc.
16// Portions created by the Initial Developer are
17// Copyright (C) 2006 Cisco Systems, Inc.  All Rights Reserved.
18//
19// Contributor(s):
20//
21// END LICENSE BLOCK
22
23package com.parctechnologies.eclipse.visualisation;
24
25import com.parctechnologies.eclipse.*;
26import java.io.*;
27import java.net.SocketException;
28import java.util.*;
29import java.awt.event.*;
30import javax.swing.*;
31import java.beans.*;
32import java.lang.reflect.*;
33/**
34 * This is the top-level class of the Visualisation architecture on the
35 * Java side.<p>
36 *
37 * It is responsible for:<p>
38 * 1 intialising the visualisation client's streams (in this case queues between
39 *   Java and Eclipse).<p>
40 * 2 Registering the VC with eclipse.<p>
41 * 3 Fulfilling the VC side of the visualisation protocol<p>
42 * 4 Maintaining a VisClientStateModel instance, which reflects the current state
43 * that the VisClient is in.<p>
44 * 5 Unregistering the visualisation client and cleaning up the Java side, when
45 * termination occurs.<p>
46 * 6 Handling any exceptions which arise from communicating with ECLiPSe.<p>
47 * 7 Participating in any Multitaksing phases that ECLiPSe enters.
48 *
49 * (1) and (2) are done by the constructor, which assumes that the calling Java
50 * thread has control (rather than Eclipse or some other Java thread) and can
51 * therefore invoke methods such as rpc on the EclipseConnection.<p>
52 *
53 * (3) and (4) work as follows:<p>
54 *
55 * There are two QueueListeners on the two fromEclipse queues (viewables
56 * and updates). These are responsible for dealing with the various messages
57 * which appear according to the visualisation protocol on these queues.<p>
58 *
59 * The basic way this works is to <p>
60 *
61 * a) "parse" the message, turning it from an EXDR
62 * structure into a VisEvent object.<p>
63 *
64 * b) "process" the event. This mainly involves calling the processEvent method,
65 * but there are also various other things to do based on the event type, for
66 * example getting the new size of the viewable in the case of
67 * create/expand/contract events. <p>
68 *
69 * processEvent is really the top-level method which is invoked when a
70 * visualisation event happens. For each event that happens, the VisClient
71 * progresses through a common sequence of states. The processEvent method
72 * "directs" the state sequence of the
73 * visualisation client throughout the duration of the VisEvent. However, the
74 * state itself is encapsulated in the VisClientStateModel, which is passed by
75 * reference to various other objects which want to be aware of changes to the
76 * state. <p>
77 *
78 * The state progression for the VC can be seen from the structure of
79 * processEvent. During the event, most of the actual Viewer-related work
80 * relating to the changes of state is delegated to a ViewerManager object,
81 * which can see the state via the VisClientStateModel. Each stage of the state
82 * progression has a corresponding
83 * static int constant in VisClientStateModel. Transitions between states
84 * correspond to invocations of methods in ViewerManager
85 * <p>
86 * <IMG src="doc-files/VisClient-1.png" ALT="state transition diagram"></IMG>
87 * <p>
88 * <ul>
89 * <li> start off in state NO_CURRENT_EVENT
90 * <li> message arrives on a queue, VC now has control, go to state
91 * SETTING_VIEWER_POLICY
92 * <li> If the event is a CREATE_EVENT
93 *   <ul>
94 *   <li> User is given option to selected a saved Scenario for playback
95 *   <li> ViewerManager.configureViewerBuildingPolicy is called to initialise
96 *   the Viewers for this viewable
97 *   </ul>
98 * </li>
99 * <li> message arrives on a queue, VC now has control, go to state
100 * COLLECTING_PRE_EVENT_GOALS.
101 * <li> ViewerManager.prepareForEvent is called
102 * <li> collect the preEvent goals from the ViewerManager
103 * <li> Eclipse has control, move to state EXECUTING_PRE_EVENT_GOALS
104 * <li> execute the goals
105 * <li> VC has control again
106 * <li> distribute the goal results to the viewerManager
107 * <li> call ViewerManager.startEvent
108 * <li> if we want to hold for this event, set a flag on the ECLiPSe side by means of an RPC (this flag will cause ECLiPSe to hit a breakpoint immediately control is returned to it),
109 * otherwise go directly to state EVENT_IS_FINISHED.
110 * <li> call ViewerManager.stopEvent
111 * <li> return to state NO_CURRENT_EVENT
112 * <li> ECLiPSe now has control again
113 * <li> If we had set the breakpoint (indicating that we want to hold) then
114 * the state is set to HELD_ON_EVENT and a Multitaksing phase will begin.
115 * <li> As ECLiPSe polls registered peers during the multitask phase, the
116 * VC will recieve multitask messages through the VisMultitaskListener.
117 * When the multitask phase starts, the
118 * state flag "multitaskPhase" is set to true (thus enabling the resume
119 * button) and any RPCs which need to be performed can be done so (eg.
120 * Changes to the update granularity, or pre-build goals for new Viewlets).
121 * <li>Should another peer end the multitasking phase or should the Resume
122 * action be performed (by pressing the resume button) the stopEvent()
123 * method is called which sets the state back to NO_CURRENT_EVENT.
124 * </ul>
125 *
126 *
127 * (5): termination can happen due to a user event when Eclipse has control. This
128 * is handled by the setTerminate method within VisClientStateModel. Termination
129 * can also happen when the VC has control, either due to a user event or when a
130 * specific "terminate" message has been received from ECLiPSe. In either of
131 * these cases the terminate method is invoked, which does the necessary
132 * cleaning up.<p>
133 *
134 * (6): since almost all communication with the EclipseConnection is via this class,
135 * it handles exceptions arising from lost connections, terminated Eclipses
136 * or violations of the visualisation protocol using some generic recovery methods .<p>
137 *
138 * The final stage of termination is performed by the exitNormal method in the
139 * case where the VisClient meant to exit, and exitError if we are exiting as a
140 * result of an exception being thrown.
141 *
142 * (7): Consists of
143 * <ol>
144 *   <li>Enabling the 'resume' button whenever a multitaksing phase is started</li>
145 *   <li>Setting visualisation breakpoints whenever the VC wishes to 'hold'.
146 * </ol>
147 *
148 *
149 *
150 */
151public class VisClient
152{
153
154  protected VisClientStateModel stateModel;
155  private FromEclipseQueue viewables_stream;
156  private String viewables_stream_name;
157  private FromEclipseQueue updates_stream;
158  private String updates_stream_name;
159  private ToEclipseQueue interest_stream;
160  private String interest_stream_name;
161  private EXDROutputStream interest_stream_f;
162  protected Atom clientName;
163
164  protected static final String MULTITASK_PHASE_TYPE = "vis_event" ;
165
166  private static final Atom terminateAtom = new Atom("terminate");
167  private static final Atom visProtocolSupportedAtom =
168    new Atom("vis_protocol_supported");
169  private static final Atom vcSupportAtom = new Atom("vc_support");
170  private static final Atom viewGranularityAtom =
171      new Atom("view_granularity");
172
173  private VisClient visClient;
174  private ViewerManager viewerManager;
175
176  private static final int SUPPORTED_PROTOCOL_VERSION = 1;
177
178  private boolean protocol_version_supported;
179
180
181  private EclipseMultitaskConnection eclipse;
182
183  private VisEvent currentEvent;
184
185  /**
186   * Set up a java VC based on EclipseConnection eclipse. Assumes that the
187   * thread calling this constructor has access to rpc etc on the
188   * EclipseConnection (i.e. the eclipse does not have control and is not
189   * "owned" by some other java thread).
190   */
191  public VisClient(EclipseMultitaskConnection eclipse)
192  {
193    this.eclipse = eclipse;
194    //DebuggingSupport.logMessage(this,"VisClient called with "+eclipse);
195    try
196    {
197      initialise();
198    }
199    catch(EclipseException ee){recover_ee(ee);}
200    catch(IOException ioe){recover_ioe(ioe);}
201    catch(VisException ve){recover_ve(ve);}
202  }
203
204  private void processEvent(VisEvent visEvent)
205  {
206    // store event for stopEvent method
207    currentEvent = visEvent;
208
209    if (DebuggingSupport.logMessages) {
210	DebuggingSupport.logMessage(this, visEvent);
211    }
212
213    stateModel.setEclipseHasControl(false);
214
215    String viewableName = visEvent.getViewableName();
216
217    stateModel.setCurrentState(VisClientStateModel.SETTING_VIEWER_POLICY);
218    if (visEvent instanceof CreateEvent) {
219        ScenarioManager.getInstance().selectPlaybackScenario(viewableName);
220        // Clear the policy flag so that the user will be prompted
221        // unless the scenario that they selected (if any) sets it
222        // as a result of the CREATE_EVENT
223        stateModel.setViewerBuildingPolicySelected(false);
224    }
225    // Hand the event off to the scenario manager which will either record
226    // the event or replay some commands or both
227    ScenarioManager.getInstance().processEvent(visEvent);
228    if (visEvent instanceof CreateEvent) {
229        // Incase the scenario does not set the policy, or if there is no
230        // scenario being played back, ask the user
231	viewerManager.configureViewerBuildingPolicy(viewableName,
232                                                    ((CreateEvent)visEvent).getViewableType());
233    }
234
235
236    stateModel.setCurrentState(VisClientStateModel.COLLECTING_PRE_EVENT_GOALS);
237
238
239    // Hand the event off to the scenario manager which will either record
240    // the event or replay some commands or both
241    ScenarioManager.getInstance().processEvent(visEvent);
242
243    viewerManager.prepareForEvent(visEvent);
244
245    BatchGoal preEventGoals =
246      viewerManager.collectPreEventGoals(visEvent);
247
248    List goalResults = null;
249
250    try
251    {
252      stateModel.setCurrentState(VisClientStateModel.EXECUTING_PRE_EVENT_GOALS);
253
254      // Hand the event off to the scenario manager which will either record
255      // the event or replay some commands or both
256      ScenarioManager.getInstance().processEvent(visEvent);
257
258      stateModel.setEclipseHasControl(true);
259      goalResults = preEventGoals.execute(eclipse);
260      stateModel.setEclipseHasControl(false);
261    }
262    catch(EclipseException ee){recover_ee(ee);}
263    catch(IOException ioe){recover_ioe(ioe);}
264
265    stateModel.
266      setCurrentState(VisClientStateModel.DISTRIBUTING_PRE_EVENT_GOAL_RESULTS);
267    viewerManager.startEvent(visEvent, goalResults);
268
269    stateModel.setAllScenarioCommandsExecuted(false);
270
271    // Hand the event off to the scenario manager which will either record
272    // the event or replay some commands or both
273    ScenarioManager.getInstance().processEvent(visEvent);
274
275    if (DebuggingSupport.logMessages) {
276        DebuggingSupport.logMessage(this,"invokeAndWait returns");
277    }
278
279
280    if((viewerManager.shouldHold() || stateModel.getInterrupt())
281       &&
282       !stateModel.getTerminate())
283    {
284      // Add to the swing queue a Runnable which signifies that all commands
285      // have now been executed.
286      try {
287        SwingUtilities.invokeAndWait(new AllCommandsExecuted());
288      } catch(InvocationTargetException ite) {
289        ite.printStackTrace();
290      } catch(InterruptedException ie) {
291        ie.printStackTrace();
292      }
293
294      // trigger a breakpoint
295      stateModel.setCurrentState(VisClientStateModel.SETTING_BREAKPOINT);
296      try {
297        if (DebuggingSupport.logMessages) {
298          DebuggingSupport.logMessage(this,"Setting breakpoint");
299        }
300        eclipse.rpc(":",vcSupportAtom,new Atom("vis_client_breakpoint"));
301      } catch(IOException ioe) {
302        recover_ioe(ioe);
303      } catch(EclipseException ee) {
304        recover_ee(ee);
305      } finally {
306        // Set the information line to the name of this event
307        viewerManager.holdingEvent(visEvent);
308      }
309      stateModel.setCurrentState(VisClientStateModel.HELD_ON_EVENT);
310      // Hand the event off to the scenario manager which will either record
311      // the event or replay some commands or both
312      ScenarioManager.getInstance().processEvent(visEvent);
313    } else {
314      stopEvent();
315    }
316    synchronized(stateModel) {
317      // Must be carefull here to ensure that any asyncrhronous
318      // setTerminate(true) calls are handled properly
319      if(stateModel.getTerminate()) {
320        terminate();
321      }
322      stateModel.setEclipseHasControl(true);
323    }
324  }
325
326  // To be called either by the processEvent method to set the state
327  // back to NO_CURRENT_EVENT or by the MultitaskListener at the end of
328  // a multitask phase
329  private void stopEvent() {
330    Collection interests = null;
331    VisEvent visEvent = currentEvent;
332    String viewableName = visEvent.getViewableName();
333    stateModel.
334      setCurrentState(VisClientStateModel.EVENT_IS_FINISHED);
335
336    // Hand the event off to the scenario manager which will either record
337    // the event or replay some commands or both
338    ScenarioManager.getInstance().processEvent(visEvent);
339
340    viewerManager.stopEvent();
341
342    if(visEvent instanceof CreateEvent)
343    {
344      interests =
345        viewerManager.getInterestSpecs(viewableName);
346      try
347      {
348        expressInterestsToEclipse(viewableName, interests);
349      }
350      catch(IOException ioe){recover_ioe(ioe);}
351      addInterestSynchronisers(interests);
352    }
353
354    stateModel.
355      setCurrentState(VisClientStateModel.NO_CURRENT_EVENT);
356
357    // Hand the event off to the scenario manager which will either record
358    // the event or replay some commands or both
359    ScenarioManager.getInstance().processEvent(visEvent);
360
361    if (DebuggingSupport.logMessages) {
362	DebuggingSupport.logMessage(this, "returning to ECLiPSe");
363    }
364
365    currentEvent = null;
366  }
367
368  private void addInterestSynchronisers(Collection interestsList)
369  {
370    Iterator interestsListIterator = interestsList.iterator();
371    InterestSpec interestSpec;
372    while(interestsListIterator.hasNext())
373    {
374      interestSpec = (InterestSpec) interestsListIterator.next();
375      interestSpec.getPropertyChangeSupport().
376        addPropertyChangeListener("viewGranularity",
377                                  new InterestSpecSynchroniser(interestSpec));
378    }
379  }
380
381  private void expressInterestsToEclipse(String viewableName,
382                                         Collection interestsList)
383    throws IOException
384  {
385    Atom yesNoAtom;
386    if(interestsList.isEmpty())
387    {
388      yesNoAtom = new Atom("no");
389    }
390    else
391    {
392      yesNoAtom = new Atom("yes");
393    }
394
395
396    if (DebuggingSupport.logMessages) {
397	DebuggingSupport.logMessage(this, "for viewable:"+viewableName);
398    }
399
400
401
402    if (DebuggingSupport.logMessages) {
403	DebuggingSupport.logMessage(this, "writing interest:"+interestsList);
404    }
405
406
407    interest_stream_f.write(new CompoundTermImpl(
408                                "interest",
409                                new CompoundTermImpl("viewable_create",
410                                                     new Atom(viewableName)),
411                                yesNoAtom,
412                                interestsList));
413
414    interest_stream_f.flush();
415  }
416
417  private void initialise() throws IOException, EclipseException, VisException
418  {
419    initialiseStreams();
420    initialiseQueueListeners();
421    registerVC();
422    initialiseStateModel();
423    initialiseViewerManager();
424    initialiseScenarioManager();
425  }
426
427  private void initialiseStateModel() throws EclipseException, IOException
428  {
429    stateModel = new VisClientStateModel(eclipse, clientName, this);
430
431    stateModel.setEclipseLibDir(eclipse.rpc("get_flag(installation_directory, _)").arg(2)+
432                                File.separator+"lib"+File.separator+
433                                eclipse.rpc("get_flag(hostarch,_)").arg(2));
434  }
435
436  private void initialiseViewerManager()
437  {
438    viewerManager = new ViewerManager(stateModel);
439  }
440
441  private void initialiseScenarioManager()
442  {
443    ScenarioManager.initialise(stateModel);
444  }
445
446  private void initialiseQueueListeners() throws IOException
447  {
448     viewables_stream.setListener(new ViewablesQL());
449     updates_stream.setListener(new UpdatesQL());
450  }
451
452  // note slight complication: we ensure a unique visClient name by suffixing
453  // a number.
454  private void registerVC() throws IOException, EclipseException, VisException
455  {
456    int n = 0;
457    clientName = null;
458    eclipse.rpc("ensure_loaded(library(vc_support))");
459    protocol_version_supported = false;
460    while(clientName == null)
461    {
462      interest_stream_f.write(new CompoundTermImpl("vis_protocol_version",
463                                    new Integer(SUPPORTED_PROTOCOL_VERSION)));
464      interest_stream_f.flush();
465      try
466      {
467        eclipse.rpc(":", vcSupportAtom,
468                         new CompoundTermImpl("vis_client_register",
469                         new Atom(eclipse.getPeerName().functor() + "_jvc_"+n),
470                         new Atom(viewables_stream_name),
471                         new Atom(updates_stream_name),
472                         new Atom(interest_stream_name))
473                    );
474      }
475      catch(Fail f)
476      // There are two fail cases: where the vis client name was not unique, and
477      // where the protocol is unsupported (detected by a boolean flag which will
478      // have been set by the viewables QL).
479      {
480        if(!protocol_version_supported)
481        {
482          throw new VisException("The visualisation protocol version is not supported.");
483        }
484        n++;
485      }
486      clientName = new Atom(eclipse.getPeerName().functor() + "_jvc_"+n);
487    }
488  }
489
490
491
492  private void initialiseStreams() throws IOException, EclipseException
493  {
494    // NOTE: a slight complication: we use findUnusedStreamName to ensure that
495    // the stream name is unique.
496    String root_viewables_name, root_interest_name, root_updates_name;
497
498    root_viewables_name = eclipse.getPeerName().functor() + "_jvc_viewables";
499    viewables_stream_name = findUnusedStreamName(root_viewables_name);
500    viewables_stream = eclipse.getFromEclipseQueue(viewables_stream_name);
501    viewables_stream.setListener(new ViewablesQL());
502
503    root_updates_name = eclipse.getPeerName().functor() + "_jvc_updates";
504    updates_stream_name = findUnusedStreamName(root_updates_name);
505    updates_stream = eclipse.getFromEclipseQueue(updates_stream_name);
506    updates_stream.setListener(new UpdatesQL());
507
508    root_interest_name = eclipse.getPeerName().functor() + "_jvc_interest";
509    interest_stream_name = findUnusedStreamName(root_interest_name);
510    interest_stream = eclipse.getToEclipseQueue(interest_stream_name);
511    interest_stream_f = new EXDROutputStream(interest_stream);
512
513    // Register this peer as being interested in Multitasking phases
514    eclipse.registerMultitask(new VisMultitaskListener());
515  }
516
517  private String findUnusedStreamName(String name)
518    throws IOException, EclipseException
519  {
520    String result = null;
521    int n = 0;
522    while(result == null)
523    {
524      try
525      {
526        eclipse.rpc("current_stream", new Atom(name+n));
527      }
528      catch(Fail f)
529      {
530        return(name+n);
531      }
532      n++;
533    }
534    return(name+n);
535  }
536
537
538  /**
539   * Provides access to the ViewerManager
540   */
541  public ViewerManager getViewerManager() {
542    return viewerManager;
543  }
544
545  /** Generic VisEvents queue listener: detaAvailable method sets up EXDR parsing
546   * input stream if needed and reads the next eventTerm, catching any
547   * IOException. */
548  private abstract class VisEventsQL implements QueueListener
549  {
550    protected CompoundTerm eventTerm;
551    EXDRInputStream eis = null;
552    public void dataAvailable(Object source)
553    {
554      if(eis == null)
555      {
556        FromEclipseQueue feq = (FromEclipseQueue) source;
557        eis = new EXDRInputStream(feq);
558      }
559      try
560      {
561        eventTerm = (CompoundTerm) eis.readTerm();
562      }
563      catch(IOException ioe){recover_ioe(ioe);}
564    }
565
566
567    public void dataRequest(Object source){}
568  }
569
570  /** MultitaskListener attached to the EclipseMultitaskConnection.
571      Behaviour is to set the VisClientState to indicate being in
572      multi-tasking mode */
573  private class VisMultitaskListener implements MultitaskListener
574  {
575    public void starting(EclipseMultitaskConnection eclipseCon,
576                         String type) {
577      if (DebuggingSupport.logMessages) {
578        DebuggingSupport.logMessage(this,
579                                    "VisClient multitask starting type="+type);
580      }
581      if(stateModel.getTerminate()) {
582        terminate();
583      }
584      // enable the continue button
585      stateModel.setCanPerformRPC(true);
586      if (DebuggingSupport.logMessages) {
587        DebuggingSupport.logMessage(this,
588                                    "VisClient multitask set RPC flag");
589      }
590      if (MULTITASK_PHASE_TYPE.equals(type)) {
591        // A multitask phase has begun
592        if (viewerManager.shouldHold() || stateModel.getInterrupt()) {
593          // execute peer_set_multitask
594          try {
595            eclipse.multitaskConfirm();
596          } catch(IOException ioe) {
597            recover_ioe(ioe);
598          } catch(EclipseException ee) {
599            recover_ee(ee);
600          }
601        }
602      }
603      if (DebuggingSupport.logMessages) {
604        DebuggingSupport.logMessage(this,
605                                    "VisClient multitask started type="+type);
606      }
607    }
608
609    public void ending(EclipseMultitaskConnection eclipseCon,
610                       String type) {
611      if (DebuggingSupport.logMessages) {
612        DebuggingSupport.logMessage(this,
613                                    "VisClient multitask ending type="+type);
614      }
615      if(stateModel.getTerminate()) {
616        terminate();
617      }
618      stateModel.setCanPerformRPC(false);
619      if (MULTITASK_PHASE_TYPE.equals(type)) {
620        stopEvent();
621      }
622    }
623  }
624
625
626  /** Queue listener attached to "updates" stream. Behaviour is simple: its
627   * dataAvailable method simply gets the eventTerm and processes it */
628  private class UpdatesQL extends VisEventsQL
629  {
630    public void dataAvailable(Object source)
631    {
632      super.dataAvailable(source);
633      VisEvent event = null;
634      try
635      {
636        event = UpdateEvent.parseFromCompoundTerm(eventTerm);
637      }
638      catch(VisException ve){recover_ve(ve);}
639      try {
640          processEvent(event);
641      } catch(RuntimeException re) {
642          recover_re(re);
643      }
644    }
645  }
646
647
648  private class ViewablesQL extends VisEventsQL
649  {
650  /** In the case of the viewables QL, dataAvailable is also able to handle the
651   * termination and protocol version messages which may appear on this stream */
652    public void dataAvailable(Object source)
653    {
654      super.dataAvailable(source);
655      if(eventTerm.equals(terminateAtom))
656      {
657
658	  if (DebuggingSupport.logMessages) {
659	      DebuggingSupport.logMessage(this, "read terminate atom");
660	  }
661
662        terminate();
663        return;
664      }
665      if(eventTerm.equals(visProtocolSupportedAtom))
666      {
667        protocol_version_supported = true;
668        return;
669      }
670      if(eventTerm.functor().equals("vis_protocol_version") &&
671         eventTerm.arity() == 1)
672      {
673        protocol_version_supported = false;
674        return;
675      }
676      parseAndProcess(eventTerm);
677    }
678   /** parseAndProcess is called on the eventTerm. As well as parsing the term,
679    * it also queries the viewable's size if the event type indicates that this
680    * information is required. */
681    private void parseAndProcess(CompoundTerm eventTerm)
682    {
683      VisEvent visEvent = null;
684      try
685      {
686        visEvent = VisEvent.eventFromCompoundTerm(eventTerm);
687        if(visEvent instanceof CreateEvent)
688        {
689          CompoundTermImpl sizeResult =
690            sizeGoal(visEvent);
691          ((CreateEvent) visEvent).setViewableSize((List) sizeResult.argCT(2).arg(2));
692        }
693        if(visEvent instanceof SizeEvent)
694        {
695          CompoundTermImpl sizeResult =
696            sizeGoal(visEvent);
697          ((SizeEvent) visEvent).setViewableSize((List) sizeResult.argCT(2).arg(2));
698        }
699      }
700      catch(VisException ve){recover_ve(ve);}
701      try {
702          processEvent(visEvent);
703      } catch(RuntimeException re) {
704          recover_re(re);
705      }
706    }
707
708    private CompoundTermImpl sizeGoal(VisEvent visEvent)
709    {
710      try
711      {
712        return((CompoundTermImpl) eclipse.rpc(new CompoundTermImpl(
713                          ":",
714                          vcSupportAtom,
715                          new CompoundTermImpl("viewable_size",
716                            new Atom(visEvent.getViewableName()),
717                            null))));
718      }
719      catch(EclipseException ee){recover_ee(ee);}
720      catch(IOException ioe){recover_ioe(ioe);}
721      return(null);
722    }
723
724  }
725
726  /** synchronises the view granularity between the java rep of the
727   * interest and the ECLiPSe rep. Added as a propertyChangeListener
728   * to the interestSpec as soon as an Eclipse-side rep is
729   * established. If the viewGranularity changes on the Java side, the
730   * synchroniser uses a multitasking phase RPC to modify the ECLiPSe-side
731   * interest spec. The converse direction is not synchronised at
732   * present. Actions changing the java side rep can only be performed
733   * when the stateModel is in the multitask phase and should only be
734   * enabled during this state */
735  private class InterestSpecSynchroniser implements PropertyChangeListener
736  {
737    private InterestSpec interestSpec;
738    InterestSpecSynchroniser(InterestSpec interestSpec)
739    {
740      this.interestSpec = interestSpec;
741    }
742    public void propertyChange(PropertyChangeEvent event)
743    {
744      CompoundTerm goal;
745      Atom visClientName = stateModel.getVisClientName();
746      Atom viewableName = interestSpec.getViewable().getNameAtom();
747      String interestSpecName = interestSpec.getName();
748      Atom newViewGranularity = (Atom) event.getNewValue();
749      goal = new CompoundTermImpl(":", vcSupportAtom,
750              new CompoundTermImpl("vis_client_interest_modify",
751                                   viewableName, visClientName,
752                                   new Atom(interestSpecName),
753                                   viewGranularityAtom,
754                                   newViewGranularity));
755      try {
756        stateModel.executeMultitaskGoal(goal);
757      } catch(IOException ioe){
758        recover_ioe(ioe);
759      } catch(EclipseException ee){
760        recover_ee(ee);
761      }
762    }
763
764  }
765
766  /** Generic recovery method for IOExceptions, including EclipseTerminated. */
767  protected void recover_ioe(IOException ioe)
768  {
769    if (DebuggingSupport.logMessages) {
770      ioe.printStackTrace(System.err);
771    }
772    if(ioe instanceof EclipseTerminatedException)
773    {
774      viewerManager.errorDialog("The ECLiPSe process which the visualisation"+
775                                "\nclient was connected to has terminated."+
776                                "\n\nThe visualisation client will now exit.");
777      exitError();
778    } else if(stateModel.getTerminate() && ioe instanceof SocketException) {
779      // do not print any message as this is to be expected during
780      // the termination phase
781      exitNormal();
782    }
783    else
784    {
785      viewerManager.errorDialog("The following IOException was raised: \n\n"+ioe+
786                                "\n\nThe visualisation client will now exit.");
787      exitError();
788    }
789  }
790
791  /** Generic recovery method for EclipseException. */
792  protected void recover_ee(EclipseException ee)
793  {
794    if (DebuggingSupport.logMessages) {
795      ee.printStackTrace(System.err);
796    }
797    viewerManager.errorDialog("The following EclipseException was raised: \n\n"+ee+
798                              "\n\nThe visualisation client will now exit.");
799    exitError();
800  }
801
802  /** Generic recovery method for VisException. */
803  protected void recover_ve(VisException ve)
804  {
805    if (DebuggingSupport.logMessages) {
806      ve.printStackTrace(System.err);
807    }
808    viewerManager.errorDialog("The following VisException was raised: \n\n"+ve+
809                              "\n\nThe visualisation client will now exit.");
810    exitError();
811  }
812
813  /** Generic recovery method for RuntimeException. */
814  protected void recover_re(RuntimeException re)
815  {
816    if (DebuggingSupport.logMessages) {
817      re.printStackTrace(System.err);
818    }
819    viewerManager.errorDialog("The following RuntimeException was raised: \n\n"+re+
820                              "\n\nThe visualisation client will now exit.");
821    exitError();
822  }
823
824  /** Exit due to occurrence of error. The VisClient implementation does
825   * nothing: subclasses override this behaviour according to the deployment
826   * scenario */
827  protected void exitError()
828  {
829
830      if (DebuggingSupport.logMessages) {
831	  DebuggingSupport.logMessage(this, "exit error");
832      }
833      if (stateModel.getCanPerformRPC()) {
834        endMultitaskPhase();
835      }
836  }
837
838  /** Exit due to a terminate request. The VisClient implementation does
839   * nothing: subclasses override this behaviour according to the deployment
840   * scenario */
841  protected void exitNormal()
842  {
843
844      if (DebuggingSupport.logMessages) {
845	  DebuggingSupport.logMessage(this, "exit normal");
846      }
847      if (stateModel.getCanPerformRPC()) {
848        endMultitaskPhase();
849      }
850
851  }
852
853  /**
854   * Termination intiated from the Java side (although may also follow a
855   * terminate message from the eclipse side). Tries to inform Eclipse, using
856   * a terminate message on the interest stream, then closes all streams and
857   * unregisters the VC.
858   */
859  private void terminate()
860  {
861    try
862    {
863      interest_stream_f.write(terminateAtom);
864      interest_stream_f.flush();
865      viewables_stream.close();
866      updates_stream.close();
867      interest_stream.close();
868      eclipse.rpc(new CompoundTermImpl(":", vcSupportAtom,
869                  new CompoundTermImpl("vis_client_unregister", clientName)));
870      exitNormal();
871    }
872    catch(EclipseException ee){recover_ee(ee);}
873    catch(IOException ioe){recover_ioe(ioe);}
874  }
875
876  private class AllCommandsExecuted implements Runnable {
877        public void run() {
878            stateModel.setAllScenarioCommandsExecuted(true);
879        }
880  }
881
882
883  /**
884   * Ends the multitask phase
885   */
886  void endMultitaskPhase() {
887    try {
888      eclipse.multitaskTerminate();
889    } catch(IOException ioe) {
890      recover_ioe(ioe);
891    } catch(EclipseException ee) {
892      recover_ee(ee);
893    }
894  }
895}
896