JAL-1645 Version-Rel Version 2.9 Year-Rel 2015 Licensing glob
[jalview.git] / src / jalview / gui / VamsasApplication.java
index cf44191..4630fd1 100644 (file)
@@ -1,13 +1,38 @@
-/**
- *
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9)
+ * Copyright (C) 2015 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
 package jalview.gui;
 
 import jalview.bin.Cache;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.io.VamsasAppDatastore;
+import jalview.structure.SelectionListener;
+import jalview.structure.SelectionSource;
 import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasListener;
+import jalview.structure.VamsasSource;
+import jalview.util.MessageManager;
+import jalview.viewmodel.AlignmentViewport;
 
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
@@ -15,132 +40,255 @@ import java.io.File;
 import java.io.IOException;
 import java.util.Hashtable;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 
 import javax.swing.JInternalFrame;
+import javax.swing.JOptionPane;
 
 import uk.ac.vamsas.client.ClientHandle;
 import uk.ac.vamsas.client.IClient;
 import uk.ac.vamsas.client.IClientDocument;
-import uk.ac.vamsas.client.NoDefaultSessionException;
+import uk.ac.vamsas.client.InvalidSessionDocumentException;
 import uk.ac.vamsas.client.UserHandle;
 import uk.ac.vamsas.client.VorbaId;
 import uk.ac.vamsas.client.picking.IMessageHandler;
 import uk.ac.vamsas.client.picking.IPickManager;
 import uk.ac.vamsas.client.picking.Message;
 import uk.ac.vamsas.client.picking.MouseOverMessage;
+import uk.ac.vamsas.client.picking.SelectionMessage;
 import uk.ac.vamsas.objects.core.Entry;
+import uk.ac.vamsas.objects.core.Input;
+import uk.ac.vamsas.objects.core.Pos;
+import uk.ac.vamsas.objects.core.Seg;
+
 /**
  * @author jimp
- *
+ * 
  */
-public class VamsasApplication
+public class VamsasApplication implements SelectionSource, VamsasSource
 {
-  IClient vclient=null;
-  ClientHandle app=null;
-  UserHandle user=null;
-  
+  IClient vclient = null;
+
+  ClientHandle app = null;
+
+  UserHandle user = null;
+
   Desktop jdesktop = null; // our jalview desktop reference
-  
+
+  private boolean inInitialUpdate = true;
+
   // Cache.preferences for vamsas client session arena
   // preferences for check for default session at startup.
   // user and organisation stuff.
-  public VamsasApplication(Desktop jdesktop,
-                      File sessionPath)
+  public VamsasApplication(Desktop jdesktop, File sessionPath,
+          String sessionName)
   {
     // JBPNote:
     // we should create a session URI from the sessionPath and pass it to
     // the clientFactory - but the vamsas api doesn't cope with that yet.
-    this(jdesktop);
-    throw new Error("Sorry - can't start from session file yet."); // make this a warning
+    this.jdesktop = jdesktop;
+    initClientSession(null, sessionPath, sessionName);
   }
-  private static uk.ac.vamsas.client.IClientFactory getClientFactory() throws IOException {
+
+  private static uk.ac.vamsas.client.IClientFactory getClientFactory()
+          throws IOException
+  {
     return new uk.ac.vamsas.client.simpleclient.SimpleClientFactory();
   }
+
   /**
    * Start a new vamsas session
+   * 
    * @param jdesktop
    */
   public VamsasApplication(Desktop jdesktop)
   {
-    this.jdesktop = jdesktop; 
-    initClientSession(null);
+    this.jdesktop = jdesktop;
+    initClientSession(null, null);
   }
+
   /**
    * init a connection to the session at the given url
+   * 
    * @param jdesktop
    * @param sessionUrl
    */
   public VamsasApplication(Desktop jdesktop, String sessionUrl)
   {
-    this.jdesktop = jdesktop; 
-    initClientSession(sessionUrl);
+    this.jdesktop = jdesktop;
+    initClientSession(sessionUrl, null);
   }
+
   /**
-   * @throws IOException or other if clientfactory instantiation failed.
+   * @throws IOException
+   *           or other if clientfactory instantiation failed.
    * @return list of current sessions or null if no session exists.
    */
-  public static String[] getSessionList() throws Exception {
+  public static String[] getSessionList() throws Exception
+  {
     return getClientFactory().getCurrentSessions();
   }
+
   /**
-   * initialise, possibly with e valid session url
+   * initialise, possibly with either a valid session url or a file for a new
+   * session
+   * 
    * @param sess
-   * @return
+   *          null or a valid session url
+   * @param vamsasDocument
+   *          null or a valid vamsas document file
+   * @return false if no vamsas connection was made
    */
-  private boolean initClientSession(String sess) {
-    try {
+  private void initClientSession(String sess, File vamsasDocument)
+  {
+    initClientSession(sess, vamsasDocument, null);
+  }
+
+  private boolean initClientSession(String sess, File vamsasDocument,
+          String newDocSessionName)
+  {
+    try
+    {
       // Only need to tell the library what the application is here
-      app = new ClientHandle("jalview.bin.Jalview", jalview.bin.Cache.getProperty("VERSION"));
+      app = getJalviewHandle();
       uk.ac.vamsas.client.IClientFactory clientfactory = getClientFactory();
-      if (sess==null)
+      if (vamsasDocument != null)
       {
-        vclient = clientfactory.getNewSessionIClient(app);
+        if (sess != null)
+        {
+          throw new Error(
+                  MessageManager
+                          .getString("error.implementation_error_cannot_import_vamsas_doc"));
+        }
+        try
+        {
+          if (newDocSessionName != null)
+          {
+            vclient = clientfactory.openAsNewSessionIClient(app,
+                    vamsasDocument, newDocSessionName);
+          }
+          else
+          {
+            vclient = clientfactory.openAsNewSessionIClient(app,
+                    vamsasDocument);
+          }
+        } catch (InvalidSessionDocumentException e)
+        {
+          JOptionPane
+                  .showInternalMessageDialog(
+                          Desktop.desktop,
+
+                          MessageManager
+                                  .getString("label.vamsas_doc_couldnt_be_opened_as_new_session"),
+                          MessageManager
+                                  .getString("label.vamsas_document_import_failed"),
+                          JOptionPane.ERROR_MESSAGE);
+
+        }
       }
       else
       {
-        vclient = clientfactory.getIClient(app,sess);
+        // join existing or create a new session
+        if (sess == null)
+        {
+          vclient = clientfactory.getNewSessionIClient(app);
+        }
+        else
+        {
+          vclient = clientfactory.getIClient(app, sess);
+        }
       }
-      
+      // set some properties for our VAMSAS interaction
+      setVclientConfig();
       user = vclient.getUserHandle();
-      
-    } 
-    catch (Exception e)
+
+    } catch (Exception e)
     {
-      jalview.bin.Cache.log.error("Couldn't instantiate vamsas client !",e);
+      jalview.bin.Cache.log
+              .error("Couldn't instantiate vamsas client !", e);
       return false;
     }
     return true;
   }
+
+  private void setVclientConfig()
+  {
+    if (vclient == null)
+    {
+      return;
+    }
+    try
+    {
+      if (vclient instanceof uk.ac.vamsas.client.simpleclient.SimpleClient)
+      {
+        uk.ac.vamsas.client.simpleclient.SimpleClientConfig cfg = ((uk.ac.vamsas.client.simpleclient.SimpleClient) vclient)
+                .getSimpleClientConfig();
+        cfg._validatemergedroots = false;
+        cfg._validateupdatedroots = true; // we may write rubbish otherwise.
+      }
+    } catch (Error e)
+    {
+      Cache.log
+              .warn("Probable SERIOUS VAMSAS client incompatibility - carrying on regardless",
+                      e);
+    } catch (Exception e)
+    {
+      Cache.log
+              .warn("Probable VAMSAS client incompatibility - carrying on regardless",
+                      e);
+    }
+  }
+
+  /**
+   * make the appHandle for Jalview
+   * 
+   * @return
+   */
+  private ClientHandle getJalviewHandle()
+  {
+    return new ClientHandle("jalview.bin.Jalview",
+            jalview.bin.Cache.getProperty("VERSION"));
+  }
+
   /**
    * 
    * @return true if we are registered in a vamsas session
    */
   public boolean inSession()
   {
-    return (vclient!=null);
+    return (vclient != null);
   }
+
   /**
-   * called to connect to session
-   * inits handlers, does an initial document update.
+   * called to connect to session inits handlers, does an initial document
+   * update.
    */
   public void initial_update()
   {
     if (!inSession())
     {
-      throw new Error("Impementation error! Vamsas Operations when client not initialised and connected.");
+      throw new Error(
+              MessageManager
+                      .getString("error.implementation_error_vamsas_operation_not_init"));
     }
     addDocumentUpdateHandler();
+    addStoreDocumentHandler();
     startSession();
-    Cache.log.debug("Jalview loading the Vamsas Session for the first time.");
-    dealWithDocumentUpdate(false); // we don't push an update out to the document yet.
+    inInitialUpdate = true;
+    Cache.log
+            .debug("Jalview loading the Vamsas Session for the first time.");
+    dealWithDocumentUpdate(false); // we don't push an update out to the
+    inInitialUpdate = false;
+    // document yet.
     Cache.log.debug("... finished update for the first time.");
   }
+
   /**
-   * Update all windows after a vamsas datamodel change.
-   * this could go on the desktop object!
+   * Update all windows after a vamsas datamodel change. this could go on the
+   * desktop object!
    * 
    */
-  protected void updateJalviewGui() 
+  protected void updateJalviewGui()
   {
     JInternalFrame[] frames = jdesktop.getAllFrames();
 
@@ -151,7 +299,7 @@ public class VamsasApplication
 
     try
     {
-      //REVERSE ORDER
+      // REVERSE ORDER
       for (int i = frames.length - 1; i > -1; i--)
       {
         if (frames[i] instanceof AlignFrame)
@@ -160,61 +308,81 @@ public class VamsasApplication
           af.alignPanel.alignmentChanged();
         }
       }
-    }
-    catch (Exception e)
+    } catch (Exception e)
     {
-      Cache.log.warn(
-          "Exception whilst refreshing jalview windows after a vamsas document update.",
-          e);
+      Cache.log
+              .warn("Exception whilst refreshing jalview windows after a vamsas document update.",
+                      e);
     }
   }
+
   public void push_update()
   {
-    Cache.log.info("Jalview updating to the Vamsas Session.");
-    dealWithDocumentUpdate(true);
-    /*
-    IClientDocument cdoc=null;
-    try
-    {
-      cdoc = vclient.getClientDocument();
-    }
-    catch (Exception e)
+    Thread udthread = new Thread(new Runnable()
     {
-      Cache.log.error("Failed to get client document for update.");
-      // RAISE A WARNING DIALOG
-      disableGui(false);
-      return;
-    }
-    updateVamsasDocument(cdoc);
-    updateJalviewGui();
-    cdoc.setVamsasRoots(cdoc.getVamsasRoots()); // propagate update flags back
-    vclient.updateDocument(cdoc);
-    */
-    Cache.log.info("Jalview finished updating to the Vamsas Session.");
+
+      public void run()
+      {
+        Cache.log.info("Jalview updating to the Vamsas Session.");
+
+        dealWithDocumentUpdate(true);
+        Cache.log.info("Jalview finished updating to the Vamsas Session.");
+      }
+
+    });
+    udthread.start();
   }
 
+  /**
+   * leave a session, prompting the user to save if necessary
+   */
   public void end_session()
   {
+    end_session(true);
+  }
+
+  private boolean promptUser = true;
+
+  /**
+   * leave a session, optionally prompting the user to save if necessary
+   * 
+   * @param promptUser
+   *          when true enable prompting by this application
+   */
+
+  public void end_session(boolean promptUser)
+  {
     if (!inSession())
-      throw new Error("Jalview not connected to Vamsas session.");
+    {
+      throw new Error(
+              MessageManager
+                      .getString("error.jalview_no_connected_vamsas_session"));
+    }
     Cache.log.info("Jalview disconnecting from the Vamsas Session.");
     try
     {
-      if (joinedSession) {
+      if (joinedSession)
+      {
+        boolean ourprompt = this.promptUser;
+        this.promptUser = promptUser;
         vclient.finalizeClient();
         Cache.log.info("Jalview has left the session.");
-      } else {
-        Cache.log.warn("JV Client leaving a session that's its not joined yet.");
+        this.promptUser = ourprompt; // restore default value
+      }
+      else
+      {
+        Cache.log
+                .warn("JV Client leaving a session that's its not joined yet.");
       }
-      joinedSession=false;
+      joinedSession = false;
       vclient = null;
-      app=null; user = null;
+      app = null;
+      user = null;
       jv2vobj = null;
       vobj2jv = null;
-    }
-    catch (Exception e)
+    } catch (Exception e)
     {
-      Cache.log.error("Vamsas Session finalization threw exceptions!",e);
+      Cache.log.error("Vamsas Session finalization threw exceptions!", e);
     }
   }
 
@@ -223,17 +391,41 @@ public class VamsasApplication
     Cache.log.debug("Jalview updating from sesion document ..");
     ensureJvVamsas();
     VamsasAppDatastore vds = new VamsasAppDatastore(cdoc, vobj2jv, jv2vobj,
-                                              baseProvEntry());
-    vds.updateToJalview();
+            baseProvEntry(), alRedoState);
+    try
+    {
+      vds.updateToJalview();
+    } catch (Exception e)
+    {
+      Cache.log.error("Failed to update Jalview from vamsas document.", e);
+    }
+    try
+    {
+      if (firstUpdate)
+      {
+        vds.updateJalviewFromAppdata();
+        // Comment this out to repeatedly read in data from JalviewAppData
+        // firstUpdate=false;
+      }
+    } catch (Exception e)
+    {
+      Cache.log.error(
+              "Exception when updating Jalview settings from Appdata.", e);
+    }
     Cache.log.debug(".. finished updating from sesion document.");
-    
+
   }
+
+  boolean firstUpdate = false;
+
   private void ensureJvVamsas()
   {
     if (jv2vobj == null)
     {
       jv2vobj = new IdentityHashMap();
       vobj2jv = new Hashtable();
+      alRedoState = new Hashtable();
+      firstUpdate = true;
     }
   }
 
@@ -241,49 +433,120 @@ public class VamsasApplication
    * jalview object binding to VorbaIds
    */
   IdentityHashMap jv2vobj = null;
+
   Hashtable vobj2jv = null;
-  public void updateVamsasDocument(IClientDocument doc)
+
+  Hashtable alRedoState = null;
+
+  boolean errorsDuringUpdate = false;
+
+  boolean errorsDuringAppUpdate = false;
+
+  /**
+   * update the document accessed through doc. A backup of the current object
+   * bindings is made.
+   * 
+   * @param doc
+   * @return number of views stored in document (updated and new views)
+   */
+  public int updateVamsasDocument(IClientDocument doc)
   {
+    int storedviews = 0;
     ensureJvVamsas();
+    errorsDuringUpdate = false;
+    errorsDuringAppUpdate = false;
+    backup_objectMapping();
     VamsasAppDatastore vds = new VamsasAppDatastore(doc, vobj2jv, jv2vobj,
-                                              baseProvEntry());
+            baseProvEntry(), alRedoState);
     // wander through frames
     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 
     if (frames == null)
     {
-      return;
+      return 0;
     }
+    Hashtable skipList = new Hashtable();
+    Hashtable viewset = new Hashtable();
 
     try
     {
-      //REVERSE ORDER
+      // REVERSE ORDER
       for (int i = frames.length - 1; i > -1; i--)
       {
         if (frames[i] instanceof AlignFrame)
         {
           AlignFrame af = (AlignFrame) frames[i];
-
-          // update alignment and root from frame.
-          vds.storeVAMSAS(af.getViewport(), af.getTitle());
+          if (!viewset.containsKey(af.getViewport().getSequenceSetId()))
+          {
+            // update alignment and root from frame.
+            boolean stored = false;
+            try
+            {
+              stored = vds.storeVAMSAS(af.getViewport(), af.getTitle());
+            } catch (Exception e)
+            {
+              errorsDuringUpdate = true;
+              Cache.log.error("Exception synchronizing "
+                      + af.getTitle()
+                      + " "
+                      + (af.getViewport().viewName == null ? "" : " view "
+                              + af.getViewport().viewName)
+                      + " to document.", e);
+              stored = false;
+            }
+            if (!stored)
+            { // record skip in skipList
+              skipList.put(af.getViewport().getSequenceSetId(), af);
+            }
+            else
+            {
+              storedviews++;
+              // could try to eliminate sequenceSetId from skiplist ..
+              // (skipList.containsKey(af.getViewport().getSequenceSetId()))
+              // remember sequenceSetId so we can skip all the other views on
+              // same alignment
+              viewset.put(af.getViewport().getSequenceSetId(), af);
+            }
+          }
         }
       }
-      //REVERSE ORDER
-      for (int i = frames.length - 1; i > -1; i--)
+      // REVERSE ORDER
+      // for (int i = frames.length - 1; i > -1; i--)
+      // {
+      // if (frames[i] instanceof AlignFrame)
+      // {
+      // AlignFrame af = (AlignFrame) frames[i];
+      Iterator aframes = viewset.values().iterator();
+      while (aframes.hasNext())
       {
-        if (frames[i] instanceof AlignFrame)
-        {
-          AlignFrame af = (AlignFrame) frames[i];
-
-          // add any AlignedCodonFrame mappings on this alignment to any other.
-          vds.storeSequenceMappings(af.getViewport(), af.getTitle());
-        }
+        AlignFrame af = (AlignFrame) aframes.next();
+        // add any AlignedCodonFrame mappings on this alignment to any other.
+        vds.storeSequenceMappings(af.getViewport(), af.getTitle());
       }
+    } catch (Exception e)
+    {
+      Cache.log.error("Exception synchronizing Views to Document :", e);
+      errorsDuringUpdate = true;
     }
-    catch (Exception e)
+
+    try
+    {
+      if (viewset.size() > 0)
+      {
+        // Alignment views were synchronized, so store their state in the
+        // appData, too.
+        // The skipList ensures we don't write out any alignments not actually
+        // in the document.
+        vds.setSkipList(skipList);
+        vds.updateJalviewClientAppdata();
+      }
+    } catch (Exception e)
     {
-      Cache.log.error("Vamsas Document store exception", e);
+      Cache.log.error("Client Appdata Write exception", e);
+      errorsDuringAppUpdate = true;
     }
+    vds.clearSkipList();
+    return storedviews;
   }
 
   private Entry baseProvEntry()
@@ -295,43 +558,56 @@ public class VamsasApplication
     pentry.setAction("created");
     return pentry;
   }
+
   /**
    * do a vamsas document update or update jalview from the vamsas document
-   * @param fromJalview true to update from jalview to the vamsas document
+   * 
+   * @param fromJalview
+   *          true to update from jalview to the vamsas document
+   * @return total number of stored alignments in the document after the update
    */
-  protected void dealWithDocumentUpdate(boolean fromJalview)
+  protected int dealWithDocumentUpdate(boolean fromJalview)
   {
+    int storedviews = 0;
     // called by update handler for document update.
     Cache.log.debug("Updating jalview from changed vamsas document.");
     disableGui(true);
-    try {
+    try
+    {
       long time = System.currentTimeMillis();
       IClientDocument cdoc = vclient.getClientDocument();
       if (Cache.log.isDebugEnabled())
       {
-        Cache.log.debug("Time taken to get ClientDocument = "+(System.currentTimeMillis()-time));
+        Cache.log.debug("Time taken to get ClientDocument = "
+                + (System.currentTimeMillis() - time));
         time = System.currentTimeMillis();
       }
       if (fromJalview)
       {
-        this.updateVamsasDocument(cdoc);
+        storedviews += updateVamsasDocument(cdoc);
         if (Cache.log.isDebugEnabled())
         {
-          Cache.log.debug("Time taken to update Vamsas Document from jalview\t= "+(System.currentTimeMillis()-time));
+          Cache.log
+                  .debug("Time taken to update Vamsas Document from jalview\t= "
+                          + (System.currentTimeMillis() - time));
           time = System.currentTimeMillis();
         }
         cdoc.setVamsasRoots(cdoc.getVamsasRoots());
         if (Cache.log.isDebugEnabled())
         {
-          Cache.log.debug("Time taken to set Document Roots\t\t= "+(System.currentTimeMillis()-time));
+          Cache.log.debug("Time taken to set Document Roots\t\t= "
+                  + (System.currentTimeMillis() - time));
           time = System.currentTimeMillis();
         }
-      } else
+      }
+      else
       {
         updateJalview(cdoc);
         if (Cache.log.isDebugEnabled())
         {
-          Cache.log.debug("Time taken to update Jalview from vamsas document Roots\t= "+(System.currentTimeMillis()-time));
+          Cache.log
+                  .debug("Time taken to update Jalview from vamsas document Roots\t= "
+                          + (System.currentTimeMillis() - time));
           time = System.currentTimeMillis();
         }
 
@@ -339,21 +615,30 @@ public class VamsasApplication
       vclient.updateDocument(cdoc);
       if (Cache.log.isDebugEnabled())
       {
-        Cache.log.debug("Time taken to update Session Document\t= "+(System.currentTimeMillis()-time));
+        Cache.log.debug("Time taken to update Session Document\t= "
+                + (System.currentTimeMillis() - time));
         time = System.currentTimeMillis();
       }
-      cdoc=null;
-    } catch (Exception ee) {
+      cdoc = null;
+    } catch (Exception ee)
+    {
       System.err.println("Exception whilst updating :");
       ee.printStackTrace(System.err);
+      // recover object map backup, since its probably corrupted with references
+      // to Vobjects that don't exist anymore.
+      recover_objectMappingBackup();
+      storedviews = 0;
     }
     Cache.log.debug("Finished updating from document change.");
     disableGui(false);
+    return storedviews;
   }
+
   private void addDocumentUpdateHandler()
   {
     final VamsasApplication client = this;
-    vclient.addDocumentUpdateHandler(new PropertyChangeListener() {
+    vclient.addDocumentUpdateHandler(new PropertyChangeListener()
+    {
       public void propertyChange(PropertyChangeEvent evt)
       {
         Cache.log.debug("Dealing with document update event.");
@@ -363,82 +648,480 @@ public class VamsasApplication
     });
     Cache.log.debug("Added Jalview handler for vamsas document updates.");
   }
+
+  private void addStoreDocumentHandler()
+  {
+    final VamsasApplication client = this;
+    vclient.addVorbaEventHandler(
+            uk.ac.vamsas.client.Events.DOCUMENT_REQUESTTOCLOSE,
+            new PropertyChangeListener()
+            {
+              public void propertyChange(PropertyChangeEvent evt)
+              {
+                if (client.promptUser)
+                {
+                  Cache.log
+                          .debug("Asking user if the vamsas session should be stored.");
+                  int reply = JOptionPane
+                          .showInternalConfirmDialog(
+                                  Desktop.desktop,
+                                  "The current VAMSAS session has unsaved data - do you want to save it ?",
+                                  "VAMSAS Session Shutdown",
+                                  JOptionPane.YES_NO_OPTION,
+                                  JOptionPane.QUESTION_MESSAGE);
+
+                  if (reply == JOptionPane.YES_OPTION)
+                  {
+                    Cache.log.debug("Prompting for vamsas store filename.");
+                    Desktop.instance.vamsasSave_actionPerformed(null);
+                    Cache.log
+                            .debug("Finished attempt at storing document.");
+                  }
+                  Cache.log
+                          .debug("finished dealing with REQUESTTOCLOSE event.");
+                }
+                else
+                {
+                  Cache.log
+                          .debug("Ignoring store document request (promptUser==false)");
+                }
+              }
+            });
+    Cache.log.debug("Added Jalview handler for vamsas document updates.");
+  }
+
   public void disableGui(boolean b)
   {
     Desktop.instance.setVamsasUpdate(b);
   }
-  private boolean joinedSession=false;
-  private VamsasListener picker=null;
+
+  Hashtable _backup_vobj2jv;
+
+  IdentityHashMap _backup_jv2vobj;
+
+  /**
+   * make a backup of the object mappings (vobj2jv and jv2vobj)
+   */
+  public void backup_objectMapping()
+  {
+    _backup_vobj2jv = new Hashtable(vobj2jv);
+    _backup_jv2vobj = new IdentityHashMap(jv2vobj);
+  }
+
+  /**
+   * recover original object mappings from the object mapping backup if document
+   * IO failed
+   * 
+   * @throws Error
+   *           if backup_objectMapping was not called.
+   */
+  public void recover_objectMappingBackup()
+  {
+    if (_backup_vobj2jv == null)
+    {
+      if (inInitialUpdate)
+      {
+        // nothing to recover so just
+        return;
+      }
+
+      throw new Error(
+              MessageManager
+                      .getString("error.implementation_error_cannot_recover_vamsas_object_mappings"));
+    }
+    jv2vobj.clear();
+    Iterator el = _backup_jv2vobj.entrySet().iterator();
+    while (el.hasNext())
+    {
+      java.util.Map.Entry mp = (java.util.Map.Entry) el.next();
+      jv2vobj.put(mp.getKey(), mp.getValue());
+    }
+    el = _backup_vobj2jv.entrySet().iterator();
+    while (el.hasNext())
+    {
+      java.util.Map.Entry mp = (java.util.Map.Entry) el.next();
+      vobj2jv.put(mp.getKey(), mp.getValue());
+    }
+  }
+
+  private boolean joinedSession = false;
+
+  private VamsasListener picker = null;
+
+  private SelectionListener selecter;
+
   private void startSession()
   {
     if (inSession())
     {
-      try {
+      try
+      {
         vclient.joinSession();
-        joinedSession=true;
-      }
-      catch (Exception e)
+        joinedSession = true;
+      } catch (Exception e)
       {
         // Complain to GUI
-        Cache.log.error("Failed to join vamsas session.",e);
-        vclient=null;
+        Cache.log.error("Failed to join vamsas session.", e);
+        vclient = null;
       }
-      try {
+      try
+      {
         final IPickManager pm = vclient.getPickManager();
-        final StructureSelectionManager ssm = StructureSelectionManager.getStructureSelectionManager();
-        pm.registerMessageHandler(new IMessageHandler() {
-          String last=null;
+        final StructureSelectionManager ssm = StructureSelectionManager
+                .getStructureSelectionManager(Desktop.instance);
+        final VamsasApplication me = this;
+        pm.registerMessageHandler(new IMessageHandler()
+        {
+          String last = null;
+
           public void handleMessage(Message message)
           {
-            if (message instanceof MouseOverMessage && vobj2jv!=null)
+            if (vobj2jv == null)
+            {
+              // we are not in a session yet.
+              return;
+            }
+            if (message instanceof MouseOverMessage)
             {
               MouseOverMessage mm = (MouseOverMessage) message;
-              String mstring = mm.getVorbaID()+" "+mm.getPosition();
-              if (last!=null && mstring.equals(last))
+              String mstring = mm.getVorbaID() + " " + mm.getPosition();
+              if (last != null && mstring.equals(last))
               {
                 return;
               }
-              //if (Cache.log.isDebugEnabled())
-              //{
-              //  Cache.log.debug("Received MouseOverMessage "+mm.getVorbaID()+" "+mm.getPosition());
-              //}
+              // if (Cache.log.isDebugEnabled())
+              // {
+              // Cache.log.debug("Received MouseOverMessage "+mm.getVorbaID()+"
+              // "+mm.getPosition());
+              // }
               Object jvobj = vobj2jv.get(mm.getVorbaID());
               if (jvobj != null && jvobj instanceof SequenceI)
               {
                 last = mstring;
-                // Cache.log.debug("Handling Mouse over "+mm.getVorbaID()+" bound to "+jvobj+" at "+mm.getPosition());
-                // position is in sequence or in aligned sequence ???????
-                ssm.mouseOverVamsasSequence((SequenceI) jvobj, mm.getPosition());
+                // Cache.log.debug("Handling Mouse over "+mm.getVorbaID()+"
+                // bound to "+jvobj+" at "+mm.getPosition());
+                // position is character position in aligned sequence
+                ssm.mouseOverVamsasSequence((SequenceI) jvobj,
+                        mm.getPosition(), me);
+              }
+            }
+            if (message instanceof uk.ac.vamsas.client.picking.SelectionMessage)
+            {
+              // we only care about AlignmentSequence selections
+              SelectionMessage sm = (SelectionMessage) message;
+              sm.validate();
+              System.err.println("Received\n" + sm.getRawMessage());
+              Object[] jvobjs = sm.getVorbaIDs() == null ? null
+                      : new Object[sm.getVorbaIDs().length];
+              if (jvobjs == null)
+              {
+                // TODO: rationalise : can only clear a selection over a
+                // referred to object
+                ssm.sendSelection(null, null, me);
+                return;
               }
+              Class type = null;
+              boolean send = true;
+              for (int o = 0; o < jvobjs.length; o++)
+              {
+                jvobjs[o] = vobj2jv.get(sm.getVorbaIDs()[o]);
+                if (jvobjs[o] == null)
+                {
+                  // can't cope with selections for unmapped objects
+                  continue;
+                }
+                if (type == null)
+                {
+                  type = jvobjs[o].getClass();
+                }
+                ;
+                if (type != jvobjs[o].getClass())
+                {
+                  send = false;
+                  // discard - can't cope with selections over mixed objects
+                  // continue;
+                }
+              }
+              SequenceGroup jselection = null;
+              ColumnSelection colsel = null;
+              if (type == jalview.datamodel.Alignment.class)
+              {
+                if (jvobjs.length == 1)
+                {
+                  // TODO if (sm.isNone())// send a message to select the
+                  // specified columns over the
+                  // given
+                  // alignment
+
+                  send = true;
+                }
+              }
+              if (type == jalview.datamodel.Sequence.class)
+              {
+
+                SequenceI seq;
+                boolean aligned = ((jalview.datamodel.Sequence) jvobjs[0])
+                        .getDatasetSequence() != null;
+                int maxWidth = 0;
+                if (aligned)
+                {
+                  jselection = new SequenceGroup();
+                  jselection.addSequence(
+                          seq = (jalview.datamodel.Sequence) jvobjs[0],
+                          false);
+                  maxWidth = seq.getLength();
+                }
+                for (int c = 1; aligned && jvobjs.length > 1
+                        && c < jvobjs.length; c++)
+                {
+                  if (((jalview.datamodel.Sequence) jvobjs[c])
+                          .getDatasetSequence() == null)
+                  {
+                    aligned = false;
+                    continue;
+                  }
+                  else
+                  {
+                    jselection.addSequence(
+                            seq = (jalview.datamodel.Sequence) jvobjs[c],
+                            false);
+                    if (maxWidth < seq.getLength())
+                    {
+                      maxWidth = seq.getLength();
+                    }
+
+                  }
+                }
+                if (!aligned)
+                {
+                  jselection = null;
+                  // if cardinality is greater than one then verify all
+                  // sequences are alignment sequences.
+                  if (jvobjs.length == 1)
+                  {
+                    // find all instances of this dataset sequence in the
+                    // displayed alignments containing the associated range and
+                    // select them.
+                  }
+                }
+                else
+                {
+                  jselection.setStartRes(0);
+                  jselection.setEndRes(maxWidth);
+                  // locate the alignment containing the given sequences and
+                  // select the associated ranges on them.
+                  if (sm.getRanges() != null)
+                  {
+                    int[] prange = uk.ac.vamsas.objects.utils.Range
+                            .getBounds(sm.getRanges());
+                    jselection.setStartRes(prange[0] - 1);
+                    jselection.setEndRes(prange[1] - 1);
+                    prange = uk.ac.vamsas.objects.utils.Range
+                            .getIntervals(sm.getRanges());
+                    colsel = new ColumnSelection();
+                    for (int p = 0; p < prange.length; p += 2)
+                    {
+                      int d = (prange[p] <= prange[p + 1]) ? 1 : -1;
+                      // try to join up adjacent columns to make a larger
+                      // selection
+                      // lower and upper bounds
+                      int l = (d < 0) ? 1 : 0;
+                      int u = (d > 0) ? 1 : 0;
+
+                      if (jselection.getStartRes() > 0
+                              && prange[p + l] == jselection.getStartRes())
+                      {
+                        jselection.setStartRes(prange[p + l] - 1);
+                      }
+                      if (jselection.getEndRes() <= maxWidth
+                              && prange[p + u] == (jselection.getEndRes() + 2))
+                      {
+                        jselection.setEndRes(prange[p + u] - 1);
+                      }
+                      // mark all the columns in the range.
+                      for (int sr = prange[p], er = prange[p + 1], de = er
+                              + d; sr != de; sr += d)
+                      {
+                        colsel.addElement(sr - 1);
+                      }
+                    }
+                  }
+                  send = true;
+                }
+              }
+              if (send)
+              {
+                ssm.sendSelection(jselection, colsel, me);
+              }
+              // discard message.
+              for (int c = 0; c < jvobjs.length; c++)
+              {
+                jvobjs[c] = null;
+              }
+              ;
+              jvobjs = null;
+              return;
             }
           }
         });
-        picker = new VamsasListener() {
-          SequenceI last=null;
-          int i=-1;
-          public void mouseOver(SequenceI seq, int index)
+        picker = new VamsasListener()
+        {
+          SequenceI last = null;
+
+          int i = -1;
+
+          @Override
+          public void mouseOverSequence(SequenceI seq, int index,
+                  VamsasSource source)
           {
-            if (jv2vobj==null)
+            if (jv2vobj == null)
+            {
               return;
-            if (seq!=last || i!=index)
+            }
+            if (seq != last || i != index)
             {
               VorbaId v = (VorbaId) jv2vobj.get(seq);
-              if (v!=null)
+              if (v != null)
               {
-                Cache.log.debug("Mouse over "+v.getId()+" bound to "+seq+" at "+index);
+                // this should really be a trace message.
+                // Cache.log.debug("Mouse over " + v.getId() + " bound to "
+                // + seq + " at " + index);
                 last = seq;
-                i=index;
-                MouseOverMessage message = new MouseOverMessage(v.getId(), index);
+                i = index;
+                MouseOverMessage message = new MouseOverMessage(v.getId(),
+                        index);
                 pm.sendMessage(message);
               }
             }
           }
         };
+        selecter = new SelectionListener()
+        {
+
+          public void selection(SequenceGroup seqsel,
+                  ColumnSelection colsel, SelectionSource source)
+          {
+            if (vobj2jv == null)
+            {
+              Cache.log
+                      .warn("Selection listener still active for dead session.");
+              // not in a session.
+              return;
+            }
+            if (source != me)
+            {
+              AlignmentI visal = null;
+              if (source instanceof AlignViewport)
+              {
+                visal = ((AlignmentViewport) source).getAlignment();
+              }
+              SelectionMessage sm = null;
+              if ((seqsel == null || seqsel.getSize() == 0)
+                      && (colsel == null || colsel.getSelected() == null || colsel
+                              .getSelected().size() == 0))
+              {
+                if (source instanceof AlignViewport)
+                {
+                  // the empty selection.
+                  sm = new SelectionMessage("jalview",
+                          new String[] { ((AlignmentViewport) source)
+                                  .getSequenceSetId() }, null, true);
+                }
+                else
+                {
+                  // the empty selection.
+                  sm = new SelectionMessage("jalview", null, null, true);
+                }
+              }
+              else
+              {
+                String[] vobj = new String[seqsel.getSize()];
+                int o = 0;
+                for (SequenceI sel : seqsel.getSequences(null))
+                {
+                  VorbaId v = (VorbaId) jv2vobj.get(sel);
+                  if (v != null)
+                  {
+                    vobj[o++] = v.toString();
+                  }
+                }
+                if (o < vobj.length)
+                {
+                  String t[] = vobj;
+                  vobj = new String[o];
+                  System.arraycopy(t, 0, vobj, 0, o);
+                  t = null;
+                }
+                Input range = null;
+                if (seqsel != null && colsel != null)
+                {
+                  // deparse the colsel into positions on the vamsas alignment
+                  // sequences
+                  range = new Input();
+                  if (colsel.getSelected() != null
+                          && colsel.getSelected().size() > 0
+                          && visal != null
+                          && seqsel.getSize() == visal.getHeight())
+                  {
+                    // gather selected columns outwith the sequence positions
+                    // too
+                    for (Object obj : colsel.getSelected())
+                    {
+                      int ival = ((Integer) obj).intValue();
+                      Pos p = new Pos();
+                      p.setI(ival + 1);
+                      range.addPos(p);
+                    }
+                  }
+                  else
+                  {
+                    int[] intervals = colsel.getVisibleContigs(
+                            seqsel.getStartRes(), seqsel.getEndRes() + 1);
+                    for (int iv = 0; iv < intervals.length; iv += 2)
+                    {
+                      Seg s = new Seg();
+                      s.setStart(intervals[iv] + 1); // vamsas indices begin at
+                      // 1, not zero.
+                      s.setEnd(intervals[iv + 1] + 1);
+                      s.setInclusive(true);
+                      range.addSeg(s);
+                    }
+                  }
+                }
+                if (vobj.length > 0)
+                {
+                  sm = new SelectionMessage("jalview", vobj, range);
+                }
+                else
+                {
+                  sm = null;
+                }
+              }
+              if (sm != null)
+              {
+                sm.validate(); // debug
+                Cache.log.debug("Selection Message\n" + sm.getRawMessage());
+                pm.sendMessage(sm);
+              }
+            }
+          }
+
+        };
         ssm.addStructureViewerListener(picker); // better method here
+        ssm.addSelectionListener(selecter);
       } catch (Exception e)
       {
-        Cache.log.error("Failed to init Vamsas Picking",e);
+        Cache.log.error("Failed to init Vamsas Picking", e);
       }
     }
   }
+
+  public String getCurrentSession()
+  {
+    if (vclient != null)
+    {
+      return (vclient.getSessionUrn());
+    }
+    return null;
+  }
 }