JAL-1764 save Chimera/Jmol state as separate entries in project Jar
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 10 Jun 2015 10:23:17 +0000 (11:23 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 10 Jun 2015 10:23:17 +0000 (11:23 +0100)
src/jalview/datamodel/StructureViewerModel.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AppJmol.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/Desktop.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/StructureViewerBase.java

index 098372b..c23e320 100644 (file)
@@ -27,6 +27,8 @@ public class StructureViewerModel
 
   private String stateData = "";
 
+  private String viewId;
+
   private Map<File, StructureData> fileData = new HashMap<File, StructureData>();
 
   public class StructureData
@@ -36,7 +38,6 @@ public class StructureViewerModel
     private String pdbId;
 
     private List<SequenceI> seqList;
-
     // TODO and possibly a list of chains?
 
     /**
@@ -85,7 +86,7 @@ public class StructureViewerModel
 
   public StructureViewerModel(int x, int y, int width, int height,
           boolean alignWithPanel, boolean colourWithAlignPanel,
-          boolean colourByViewer)
+          boolean colourByViewer, String viewId)
   {
     this.x = x;
     this.y = y;
@@ -94,6 +95,7 @@ public class StructureViewerModel
     this.alignWithPanel = alignWithPanel;
     this.colourWithAlignPanel = colourWithAlignPanel;
     this.colourByViewer = colourByViewer;
+    this.viewId = viewId;
   }
 
   public int getX()
@@ -186,4 +188,8 @@ public class StructureViewerModel
     this.fileData = fileData;
   }
 
+  public String getViewId()
+  {
+    return this.viewId;
+  }
 }
index ad2bbc3..13a4934 100644 (file)
@@ -1098,11 +1098,6 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 .lastIndexOf(java.io.File.separatorChar) + 1);
       }
 
-      /*
-       * First save any linked Chimera session.
-       */
-      Desktop.instance.saveChimeraSessions(file);
-
       success = new Jalview2XML().saveAlignment(this, file, shortName);
 
       statusBar.setText(MessageManager.formatMessage(
index a3f6778..6139669 100644 (file)
@@ -57,6 +57,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
@@ -1234,4 +1235,10 @@ public class AppJmol extends StructureViewerBase
     return jmb == null ? null : jmb.viewer.getStateInfo();
   }
 
+  @Override
+  public ViewerType getViewerType()
+  {
+    return ViewerType.JMOL;
+  }
+
 }
index b933345..1e6da78 100644 (file)
@@ -26,9 +26,11 @@ import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -55,6 +57,7 @@ import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
@@ -445,23 +448,25 @@ public class ChimeraViewFrame extends StructureViewerBase
 
   /**
    * Create a new viewer from saved session state data including Chimera session
-   * file.
-   * 
-   * @param chimeraSession
+   * file
    * 
+   * @param chimeraSessionFile
    * @param alignPanel
    * @param pdbArray
    * @param seqsArray
    * @param colourByChimera
    * @param colourBySequence
+   * @param newViewId
    */
-  public ChimeraViewFrame(String chimeraSession, AlignmentPanel alignPanel,
+  public ChimeraViewFrame(String chimeraSessionFile,
+          AlignmentPanel alignPanel,
           PDBEntry[] pdbArray,
           SequenceI[][] seqsArray, boolean colourByChimera,
-          boolean colourBySequence)
+          boolean colourBySequence, String newViewId)
   {
     super();
-    this.chimeraSessionFile = chimeraSession;
+    setViewId(newViewId);
+    this.chimeraSessionFile = chimeraSessionFile;
     openNewChimera(alignPanel, pdbArray, seqsArray);
     if (colourByChimera)
     {
@@ -1310,31 +1315,74 @@ public class ChimeraViewFrame extends StructureViewerBase
   }
 
   /**
-   * Ask Chimera to save its session to the designated file path. Returns true
-   * if successful, else false.
+   * Ask Chimera to save its session to the designated file path, or to a
+   * temporary file if the path is null. Returns the file path if successful,
+   * else null.
    * 
    * @param filepath
    * @see getStateInfo
    */
-  public boolean saveSession(String filepath)
+  protected String saveSession(String filepath)
   {
-    boolean result = jmb.saveSession(filepath);
-    if (result)
+    String pathUsed = filepath;
+    try
+    {
+      if (pathUsed == null)
+      {
+        File tempFile = File.createTempFile("chimera", ".py");
+        tempFile.deleteOnExit();
+        pathUsed = tempFile.getPath();
+      }
+      boolean result = jmb.saveSession(pathUsed);
+      if (result)
+      {
+        this.chimeraSessionFile = pathUsed;
+        return pathUsed;
+      }
+    } catch (IOException e)
     {
-      this.chimeraSessionFile = filepath;
     }
-    return result;
+    return null;
   }
 
   /**
-   * Returns the file path of the Chimera session file the last time it was
-   * saved. If it was never saved, returns an empty string. There is no
-   * guarantee that the Chimera session has not changed since it was saved.
+   * Returns a string representing the state of the Chimera session. This is
+   * done by requesting Chimera to save its session to a temporary file, then
+   * reading the file contents. Returns an empty string on any error.
    */
   @Override
   public String getStateInfo()
   {
-    return this.chimeraSessionFile == null ? "" : chimeraSessionFile;
+    String sessionFile = saveSession(null);
+    if (sessionFile == null)
+    {
+      return "";
+    }
+    InputStream is = null;
+    try
+    {
+      File f = new File(sessionFile);
+      byte[] bytes = new byte[(int) f.length()];
+      is = new FileInputStream(sessionFile);
+      is.read(bytes);
+      return new String(bytes);
+    } catch (IOException e)
+    {
+      return "";
+    } finally
+    {
+      if (is != null)
+      {
+        try
+        {
+          is.close();
+        } catch (IOException e)
+        {
+          // ignoreß
+        }
+      }
+    }
+    // return this.chimeraSessionFile == null ? "" : chimeraSessionFile;
   }
 
   @Override
@@ -1342,4 +1390,10 @@ public class ChimeraViewFrame extends StructureViewerBase
   {
     jmb.focusView();
   }
+
+  @Override
+  public ViewerType getViewerType()
+  {
+    return ViewerType.CHIMERA;
+  }
 }
index e8d7ffd..9e84407 100644 (file)
@@ -1485,9 +1485,6 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       final java.io.File choice = chooser.getSelectedFile();
       setProjectFile(choice);
 
-      // TODO or move inside the new Thread?
-      saveChimeraSessions(choice.getAbsolutePath());
-
       new Thread(new Runnable()
       {
         public void run()
@@ -1525,32 +1522,6 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     }
   }
 
-  /**
-   * Request any open, linked Chimera sessions to save their state.
-   * 
-   * @param jalviewProjectFilename
-   *          the filename of the Jalview project; Chimera session files should
-   *          be given distinct, but obviously related, names.
-   */
-  public void saveChimeraSessions(String jalviewProjectFilename)
-  {
-    int i = 0;
-    for (JInternalFrame frame : getAllFrames())
-    {
-      if (frame instanceof ChimeraViewFrame)
-      {
-        /*
-         * Construct a filename for the Chimera session by append _chimera<n>.py
-         * to the Jalview project file name.
-         */
-        String chimeraPath = jalviewProjectFilename + "_chimera_" + i
-                + ".py";
-        ((ChimeraViewFrame) frame).saveSession(chimeraPath);
-        i++;
-      }
-    }
-  }
-
   private void setProjectFile(File choice)
   {
     this.projectFile = choice;
index f56e531..ce81e1c 100644 (file)
  */
 package jalview.gui;
 
+import java.awt.Rectangle;
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+
+import javax.swing.JInternalFrame;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+import org.exolab.castor.xml.Marshaller;
+import org.exolab.castor.xml.Unmarshaller;
+
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignedCodonFrame;
@@ -30,6 +69,7 @@ import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.StructureViewerModel;
 import jalview.datamodel.StructureViewerModel.StructureData;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.schemabinding.version2.AlcodMap;
 import jalview.schemabinding.version2.AlcodonFrame;
 import jalview.schemabinding.version2.Annotation;
@@ -83,44 +123,6 @@ import jalview.ws.params.ArgumentI;
 import jalview.ws.params.AutoCalcSetting;
 import jalview.ws.params.WsParamSetI;
 
-import java.awt.Rectangle;
-import java.io.BufferedReader;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.Vector;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-import java.util.jar.JarOutputStream;
-
-import javax.swing.JInternalFrame;
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
-
-import org.exolab.castor.xml.Unmarshaller;
-
 /**
  * Write out the current jalview desktop state as a Jalview XML stream.
  * 
@@ -133,6 +135,8 @@ import org.exolab.castor.xml.Unmarshaller;
  */
 public class Jalview2XML
 {
+  private static final String UTF_8 = "UTF-8";
+
   /*
    * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
    * of sequence objects are created.
@@ -380,7 +384,7 @@ public class Jalview2XML
       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
       // //////////////////////////////////////////////////
 
-      Vector shortNames = new Vector();
+      List<String> shortNames = new ArrayList<String>();
 
       // REVERSE ORDER
       for (int i = frames.length - 1; i > -1; i--)
@@ -394,33 +398,7 @@ public class Jalview2XML
           continue;
         }
 
-        String shortName = af.getTitle();
-
-        if (shortName.indexOf(File.separatorChar) > -1)
-        {
-          shortName = shortName.substring(shortName
-                  .lastIndexOf(File.separatorChar) + 1);
-        }
-
-        int count = 1;
-
-        while (shortNames.contains(shortName))
-        {
-          if (shortName.endsWith("_" + (count - 1)))
-          {
-            shortName = shortName.substring(0, shortName.lastIndexOf("_"));
-          }
-
-          shortName = shortName.concat("_" + count);
-          count++;
-        }
-
-        shortNames.addElement(shortName);
-
-        if (!shortName.endsWith(".xml"))
-        {
-          shortName = shortName + ".xml";
-        }
+        String shortName = makeFilename(af, shortNames);
 
         int ap, apSize = af.alignPanels.size();
 
@@ -467,6 +445,47 @@ public class Jalview2XML
     }
   }
 
+  /**
+   * Generates a distinct file name, based on the title of the AlignFrame, by
+   * appending _n for increasing n until an unused name is generated. The new
+   * name (without its extension) is added to the list.
+   * 
+   * @param af
+   * @param namesUsed
+   * @return the generated name, with .xml extension
+   */
+  protected String makeFilename(AlignFrame af, List<String> namesUsed)
+  {
+    String shortName = af.getTitle();
+
+    if (shortName.indexOf(File.separatorChar) > -1)
+    {
+      shortName = shortName.substring(shortName
+              .lastIndexOf(File.separatorChar) + 1);
+    }
+
+    int count = 1;
+
+    while (namesUsed.contains(shortName))
+    {
+      if (shortName.endsWith("_" + (count - 1)))
+      {
+        shortName = shortName.substring(0, shortName.lastIndexOf("_"));
+      }
+
+      shortName = shortName.concat("_" + count);
+      count++;
+    }
+
+    namesUsed.add(shortName);
+
+    if (!shortName.endsWith(".xml"))
+    {
+      shortName = shortName + ".xml";
+    }
+    return shortName;
+  }
+
   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
   public boolean saveAlignment(AlignFrame af, String jarFile,
           String fileName)
@@ -735,7 +754,8 @@ public class Jalview2XML
           jalview.datamodel.PDBEntry entry = (jalview.datamodel.PDBEntry) en
                   .nextElement();
 
-          pdb.setId(entry.getId());
+          String pdbId = entry.getId();
+          pdb.setId(pdbId);
           pdb.setType(entry.getType());
 
           /*
@@ -753,6 +773,24 @@ public class Jalview2XML
               StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
               matchedFile = saveStructureState(ap, jds, pdb, entry,
                       viewIds, matchedFile, viewFrame);
+              /*
+               * Only store each structure viewer's state once in each XML
+               * document. First time through only (storeDS==false)
+               */
+              String viewId = viewFrame.getViewId();
+              if (!storeDS && !viewIds.contains(viewId))
+              {
+                viewIds.add(viewId);
+                try
+                {
+                  writeJarEntry(jout, "viewer_" + viewId,
+                          viewFrame.getStateInfo().getBytes());
+                } catch (IOException e)
+                {
+                  System.err.println("Error saving viewer state: "
+                          + e.getMessage());
+                }
+              }
             }
           }
 
@@ -769,46 +807,14 @@ public class Jalview2XML
               pdbfiles = new ArrayList<String>();
             }
 
-            if (!pdbfiles.contains(entry.getId()))
+            if (!pdbfiles.contains(pdbId))
             {
-              pdbfiles.add(entry.getId());
-              DataInputStream dis = null;
-              try
-              {
-                File file = new File(matchedFile);
-                if (file.exists() && jout != null)
-                {
-                  byte[] data = new byte[(int) file.length()];
-                  jout.putNextEntry(new JarEntry(entry.getId()));
-                  dis = new DataInputStream(new FileInputStream(file));
-                  dis.readFully(data);
-
-                  DataOutputStream dout = new DataOutputStream(jout);
-                  dout.write(data, 0, data.length);
-                  dout.flush();
-                  jout.closeEntry();
-                }
-              } catch (Exception ex)
-              {
-                ex.printStackTrace();
-              } finally
-              {
-                if (dis != null)
-                {
-                  try
-                  {
-                    dis.close();
-                  } catch (IOException e)
-                  {
-                    // ignore
-                  }
-                }
-              }
-
+              pdbfiles.add(pdbId);
+              copyFileToJar(jout, matchedFile, pdbId);
             }
           }
 
-          if (entry.getProperty() != null)
+          if (entry.getProperty() != null && !entry.getProperty().isEmpty())
           {
             PdbentryItem item = new PdbentryItem();
             Hashtable properties = entry.getProperty();
@@ -935,6 +941,25 @@ public class Jalview2XML
         }
       }
     }
+
+    /*
+     * Save associated Varna panels
+     */
+    if (Desktop.desktop != null)
+    {
+      for (JInternalFrame frame : Desktop.desktop.getAllFrames())
+      {
+        if (frame instanceof AppVarna)
+        {
+          AppVarna vp = (AppVarna) frame;
+          if (vp.ap == ap)
+          {
+            // save Varna state
+          }
+        }
+      }
+    }
+
     // SAVE ANNOTATIONS
     /**
      * store forward refs from an annotationRow to any groups
@@ -1288,9 +1313,8 @@ public class Jalview2XML
         JarEntry entry = new JarEntry(fileName);
         jout.putNextEntry(entry);
         PrintWriter pout = new PrintWriter(new OutputStreamWriter(jout,
-                "UTF-8"));
-        org.exolab.castor.xml.Marshaller marshaller = new org.exolab.castor.xml.Marshaller(
-                pout);
+                UTF_8));
+        Marshaller marshaller = new Marshaller(pout);
         marshaller.marshal(object);
         pout.flush();
         jout.closeEntry();
@@ -1304,6 +1328,66 @@ public class Jalview2XML
   }
 
   /**
+   * Copy the contents of a file to a new file added to the output jar
+   * 
+   * @param jout
+   * @param infilePath
+   * @param jarfileName
+   */
+  protected void copyFileToJar(JarOutputStream jout, String infilePath,
+          String jarfileName)
+  {
+    DataInputStream dis = null;
+    try
+    {
+      File file = new File(infilePath);
+      if (file.exists() && jout != null)
+      {
+        dis = new DataInputStream(new FileInputStream(file));
+        byte[] data = new byte[(int) file.length()];
+        dis.readFully(data);
+        writeJarEntry(jout, jarfileName, data);
+      }
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
+    } finally
+    {
+      if (dis != null)
+      {
+        try
+        {
+          dis.close();
+        } catch (IOException e)
+        {
+          // ignore
+        }
+      }
+    }
+  }
+
+  /**
+   * Write the data to a new entry of given name in the output jar file
+   * 
+   * @param jout
+   * @param jarfileName
+   * @param data
+   * @throws IOException
+   */
+  protected void writeJarEntry(JarOutputStream jout, String jarfileName,
+          byte[] data) throws IOException
+  {
+    if (jout != null)
+    {
+      jout.putNextEntry(new JarEntry(jarfileName));
+      DataOutputStream dout = new DataOutputStream(jout);
+      dout.write(data, 0, data.length);
+      dout.flush();
+      jout.closeEntry();
+    }
+  }
+
+  /**
    * Save the state of a structure viewer
    * 
    * @param ap
@@ -1321,6 +1405,11 @@ public class Jalview2XML
           String matchedFile, StructureViewerBase viewFrame)
   {
     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
+
+    /*
+     * Look for any bindings for this viewer to the PDB file of interest
+     * (including part matches excluding chain id)
+     */
     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
     {
       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
@@ -1329,6 +1418,9 @@ public class Jalview2XML
               && !(entry.getId().length() > 4 && entry.getId()
                       .toLowerCase().startsWith(pdbId.toLowerCase())))
       {
+        /*
+         * not interested in a binding to a different PDB entry here
+         */
         continue;
       }
       if (matchedFile == null)
@@ -1346,7 +1438,6 @@ public class Jalview2XML
       // can get at it if the ID
       // match is ambiguous (e.g.
       // 1QIP==1qipA)
-      String statestring = viewFrame.getStateInfo();
 
       for (int smap = 0; smap < viewFrame.getBinding().getSequence()[peid].length; smap++)
       {
@@ -1364,18 +1455,9 @@ public class Jalview2XML
           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
           state.setColourByJmol(viewFrame.isColouredByViewer());
-          /*
-           * Only store each structure viewer's state once in each XML document.
-           */
-          if (!viewIds.contains(viewId))
-          {
-            viewIds.add(viewId);
-            state.setContent(statestring.replaceAll("\n", ""));
-          }
-          else
-          {
-            state.setContent("# duplicate state");
-          }
+          // FIXME add attribute to schema and enable next line
+          // state.setType(viewFrame.getViewerType().toString());
+          state.setContent(viewFrame.getViewerType().toString());
           pdb.addStructureState(state);
         }
       }
@@ -2067,7 +2149,7 @@ public class Jalview2XML
 
         if (jarentry != null && jarentry.getName().endsWith(".xml"))
         {
-          InputStreamReader in = new InputStreamReader(jin, "UTF-8");
+          InputStreamReader in = new InputStreamReader(jin, UTF_8);
           JalviewModel object = new JalviewModel();
 
           Unmarshaller unmar = new Unmarshaller(object);
@@ -2319,6 +2401,16 @@ public class Jalview2XML
    */
   private final boolean updateLocalViews = false;
 
+  /**
+   * Returns the path to a temporary file holding the PDB file for the given PDB
+   * id. The first time of asking, searches for a file of that name in the
+   * Jalview project jar, and copies it to a new temporary file. Any repeat
+   * requests just return the path to the file previously created.
+   * 
+   * @param jprovider
+   * @param pdbId
+   * @return
+   */
   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId)
   {
     if (alreadyLoadedPDB.containsKey(pdbId))
@@ -2326,6 +2418,31 @@ public class Jalview2XML
       return alreadyLoadedPDB.get(pdbId).toString();
     }
 
+    String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb");
+    if (tempFile != null)
+    {
+      alreadyLoadedPDB.put(pdbId, tempFile);
+    }
+    return tempFile;
+  }
+
+  /**
+   * Copies the jar entry of given name to a new temporary file and returns the
+   * path to the file, or null if the entry is not found.
+   * 
+   * @param jprovider
+   * @param jarEntryName
+   * @param prefix
+   *          a prefix for the temporary file name, must be at least three
+   *          characters long
+   * @return
+   */
+  protected String copyJarEntry(jarInputStreamProvider jprovider,
+          String jarEntryName, String prefix)
+  {
+    BufferedReader in = null;
+    PrintWriter out = null;
+
     try
     {
       JarInputStream jin = jprovider.getJarInputStream();
@@ -2339,38 +2456,46 @@ public class Jalview2XML
       do
       {
         entry = jin.getNextJarEntry();
-      } while (entry != null && !entry.getName().equals(pdbId));
+      } while (entry != null && !entry.getName().equals(jarEntryName));
       if (entry != null)
       {
-        BufferedReader in = new BufferedReader(new InputStreamReader(jin));
-        File outFile = File.createTempFile("jalview_pdb", ".txt");
+        in = new BufferedReader(new InputStreamReader(jin, UTF_8));
+        File outFile = File.createTempFile(prefix, ".tmp");
         outFile.deleteOnExit();
-        PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
+        out = new PrintWriter(new FileOutputStream(outFile));
         String data;
 
         while ((data = in.readLine()) != null)
         {
           out.println(data);
         }
-        try
-        {
-          out.flush();
-        } catch (Exception foo)
-        {
-        }
-        ;
-        out.close();
+        out.flush();
         String t = outFile.getAbsolutePath();
-        alreadyLoadedPDB.put(pdbId, t);
         return t;
       }
       else
       {
-        warn("Couldn't find PDB file entry in Jalview Jar for " + pdbId);
+        warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
       }
     } catch (Exception ex)
     {
       ex.printStackTrace();
+    } finally
+    {
+      if (in != null)
+      {
+        try
+        {
+          in.close();
+        } catch (IOException e)
+        {
+          // ignore
+        }
+      }
+      if (out != null)
+      {
+        out.close();
+      }
     }
 
     return null;
@@ -3184,8 +3309,9 @@ public class Jalview2XML
             }
             if (!structureViewers.containsKey(sviewid))
             {
-              structureViewers.put(sviewid, new StructureViewerModel(x, y,
-                      width, height, false, false, true));
+              structureViewers.put(sviewid,
+                      new StructureViewerModel(x, y, width, height, false,
+                              false, true, structureState.getViewId()));
               // Legacy pre-2.7 conversion JAL-823 :
               // do not assume any view has to be linked for colour by
               // sequence
@@ -3255,8 +3381,16 @@ public class Jalview2XML
     // Instantiate the associated structure views
     for (Entry<String, StructureViewerModel> entry : structureViewers
             .entrySet())
+    {
+      try
+      {
+        createOrLinkStructureViewer(entry, af, ap, jprovider);
+      } catch (Exception e)
       {
-      createOrLinkStructureViewer(entry, af, ap);
+        System.err.println("Error loading structure viewer: "
+                + e.getMessage());
+        // failed - try the next one
+      }
     }
   }
 
@@ -3265,12 +3399,13 @@ public class Jalview2XML
    * @param viewerData
    * @param af
    * @param ap
+   * @param jprovider
    */
   protected void createOrLinkStructureViewer(
           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
-          AlignmentPanel ap)
+          AlignmentPanel ap, jarInputStreamProvider jprovider)
   {
-    final StructureViewerModel svattrib = viewerData.getValue();
+    final StructureViewerModel stateData = viewerData.getValue();
 
     /*
      * Search for any viewer windows already open from other alignment views
@@ -3280,68 +3415,77 @@ public class Jalview2XML
 
     if (comp != null)
     {
-      linkStructureViewer(ap, comp, svattrib);
+      linkStructureViewer(ap, comp, stateData);
       return;
     }
 
     /*
-     * Pending an XML element for ViewerType, just check if stateData contains
-     * "chimera" (part of the chimera session filename).
+     * 2.8.2: stateData contains "chimera..." (session file name), or JMOL state
+     * string
+     * 
+     * 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
+     * "viewer_"+stateData.viewId
      */
-    if (svattrib.getStateData().indexOf("chimera") > -1)
+    // FIXME use stateData.getType() instead once schema updated
+    if (ViewerType.CHIMERA.toString().equals(stateData.getStateData())
+            || stateData.getStateData().indexOf("chimera") > -1)
     {
-      createChimeraViewer(viewerData, af);
+      createChimeraViewer(viewerData, af, jprovider);
     }
     else
     {
-      createJmolViewer(viewerData, af);
+      createJmolViewer(viewerData, af, jprovider);
     }
   }
 
   /**
    * Create a new Chimera viewer.
    * 
-   * @param viewerData
+   * @param data
    * @param af
+   * @param jprovider
    */
-  protected void createChimeraViewer(
-          Entry<String, StructureViewerModel> viewerData, AlignFrame af)
+  protected void createChimeraViewer(Entry<String, StructureViewerModel> viewerData,
+          AlignFrame af,
+          jarInputStreamProvider jprovider)
   {
-    final StructureViewerModel data = viewerData.getValue();
-    String chimeraSession = data.getStateData();
+    StructureViewerModel data = viewerData.getValue();
+    String chimeraSessionFile =  data.getStateData();
 
-    if (new File(chimeraSession).exists())
-    {
-      Set<Entry<File, StructureData>> fileData = data.getFileData()
-              .entrySet();
-      List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
-      List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
-      for (Entry<File, StructureData> pdb : fileData)
-      {
-        String filePath = pdb.getValue().getFilePath();
-        String pdbId = pdb.getValue().getPdbId();
-        // pdbs.add(new PDBEntry(filePath, pdbId));
-        pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
-        final List<SequenceI> seqList = pdb.getValue().getSeqList();
-        SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
-        allseqs.add(seqs);
-      }
-
-      boolean colourByChimera = data.isColourByViewer();
-      boolean colourBySequence = data.isColourWithAlignPanel();
-
-      // TODO can/should this be done via StructureViewer (like Jmol)?
-      final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
-      final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
-              .size()][]);
-      new ChimeraViewFrame(chimeraSession, af.alignPanel, pdbArray,
-              seqsArray, colourByChimera, colourBySequence);
-    }
-    else
-    {
-      Cache.log.error("Chimera session file " + chimeraSession
-              + " not found");
-    }
+    /*
+     * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
+     * 
+     * Note this is the 'saved' viewId as in the project file XML, _not_ the
+     * 'uniquified' sviewid used to reconstruct the viewer here
+     */
+    chimeraSessionFile = copyJarEntry(jprovider,
+            "viewer_" + data.getViewId(), "chimera");
+
+    Set<Entry<File, StructureData>> fileData = data.getFileData()
+            .entrySet();
+    List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
+    List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
+    for (Entry<File, StructureData> pdb : fileData)
+    {
+      String filePath = pdb.getValue().getFilePath();
+      String pdbId = pdb.getValue().getPdbId();
+      // pdbs.add(new PDBEntry(filePath, pdbId));
+      pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
+      final List<SequenceI> seqList = pdb.getValue().getSeqList();
+      SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
+      allseqs.add(seqs);
+    }
+
+    boolean colourByChimera = data.isColourByViewer();
+    boolean colourBySequence = data.isColourWithAlignPanel();
+
+    // TODO use StructureViewer as a factory here, see JAL-1761
+    final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
+    final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
+            .size()][]);
+    String newViewId = viewerData.getKey();
+    new ChimeraViewFrame(chimeraSessionFile, af.alignPanel, pdbArray,
+            seqsArray, colourByChimera, colourBySequence, newViewId);
   }
 
   /**
@@ -3351,13 +3495,27 @@ public class Jalview2XML
    * 
    * @param viewerData
    * @param af
+   * @param jprovider
    */
   protected void createJmolViewer(
           final Entry<String, StructureViewerModel> viewerData,
-          AlignFrame af)
+          AlignFrame af, jarInputStreamProvider jprovider)
   {
     final StructureViewerModel svattrib = viewerData.getValue();
     String state = svattrib.getStateData();
+
+    /*
+     * Pre-2.9: state element value is the Jmol state string
+     * 
+     * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
+     * + viewId
+     */
+    // FIXME use getType once Castor regenerated for new attribute
+    if (ViewerType.JMOL.toString().equals(state /* svattrib.getType() */))
+    {
+      state = readJarEntry(jprovider, "viewer_" + svattrib.getViewId());
+    }
+
     List<String> pdbfilenames = new ArrayList<String>();
     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
     List<String> pdbids = new ArrayList<String>();
@@ -3457,8 +3615,6 @@ public class Jalview2XML
             JalviewStructureDisplayI sview = null;
             try
             {
-              // JAL-1333 note - we probably can't migrate Jmol views to UCSF
-              // Chimera!
               sview = new StructureViewer(alf.alignPanel
                       .getStructureSelectionManager()).createView(
                       StructureViewer.ViewerType.JMOL, pdbf, id, sq,
@@ -3514,8 +3670,8 @@ public class Jalview2XML
                 && ((StructureViewerBase) frame).getViewId()
                         .equals(sviewid))
         {
-          comp = (AppJmol) frame;
-          // todo: break?
+          comp = (StructureViewerBase) frame;
+          break; // break added in 2.9
         }
         /*
          * Otherwise test for matching position and size of viewer frame
@@ -3525,8 +3681,8 @@ public class Jalview2XML
                 && frame.getHeight() == svattrib.getHeight()
                 && frame.getWidth() == svattrib.getWidth())
         {
-          comp = (AppJmol) frame;
-          // todo: break?
+          comp = (StructureViewerBase) frame;
+          // no break in faint hope of an exact match on viewId
         }
       }
     }
@@ -3544,15 +3700,15 @@ public class Jalview2XML
    * @param viewerColouring
    */
   protected void linkStructureViewer(AlignmentPanel ap,
-          StructureViewerBase viewer, StructureViewerModel svattrib)
+          StructureViewerBase viewer, StructureViewerModel stateData)
   {
     // NOTE: if the jalview project is part of a shared session then
     // view synchronization should/could be done here.
 
-    final boolean useinViewerSuperpos = svattrib.isAlignWithPanel();
-    final boolean usetoColourbyseq = svattrib.isColourWithAlignPanel();
-    final boolean viewerColouring = svattrib.isColourByViewer();
-    Map<File, StructureData> oldFiles = svattrib.getFileData();
+    final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
+    final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
+    final boolean viewerColouring = stateData.isColourByViewer();
+    Map<File, StructureData> oldFiles = stateData.getFileData();
 
     /*
      * Add mapping for sequences in this view to an already open viewer
@@ -4851,4 +5007,67 @@ public class Jalview2XML
   {
     skipList = skipList2;
   }
+
+  /**
+   * Reads the jar entry of given name and returns its contents, or null if the
+   * entry is not found.
+   * 
+   * @param jprovider
+   * @param jarEntryName
+   * @return
+   */
+  protected String readJarEntry(jarInputStreamProvider jprovider,
+          String jarEntryName)
+  {
+    String result = null;
+    BufferedReader in = null;
+
+    try
+    {
+      /*
+       * Reopen the jar input stream and traverse its entries to find a matching
+       * name
+       */
+      JarInputStream jin = jprovider.getJarInputStream();
+      JarEntry entry = null;
+      do
+      {
+        entry = jin.getNextJarEntry();
+      } while (entry != null && !entry.getName().equals(jarEntryName));
+
+      if (entry != null)
+      {
+        StringBuilder out = new StringBuilder(256);
+        in = new BufferedReader(new InputStreamReader(jin, UTF_8));
+        String data;
+
+        while ((data = in.readLine()) != null)
+        {
+          out.append(data);
+        }
+        result = out.toString();
+      }
+      else
+      {
+        warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
+      }
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
+    } finally
+    {
+      if (in != null)
+      {
+        try
+        {
+          in.close();
+        } catch (IOException e)
+        {
+          // ignore
+        }
+      }
+    }
+  
+    return result;
+  }
 }
index 13af0e8..1feaa7c 100644 (file)
@@ -1,8 +1,5 @@
 package jalview.gui;
 
-import jalview.gui.ViewSelectionMenu.ViewSetProvider;
-import jalview.jbgui.GStructureViewer;
-
 import java.awt.Component;
 import java.util.ArrayList;
 import java.util.List;
@@ -10,6 +7,10 @@ import java.util.Vector;
 
 import javax.swing.JMenuItem;
 
+import jalview.gui.StructureViewer.ViewerType;
+import jalview.gui.ViewSelectionMenu.ViewSetProvider;
+import jalview.jbgui.GStructureViewer;
+
 /**
  * Base class with common functionality for JMol, Chimera or other structure
  * viewers.
@@ -222,4 +223,6 @@ public abstract class StructureViewerBase extends GStructureViewer
       _colourwith.remove(nap);
     }
   }
+
+  public abstract ViewerType getViewerType();
 }