JAL-1645 Version-Rel Version 2.9 Year-Rel 2015 Licensing glob
[jalview.git] / src / jalview / gui / Jalview2XML.java
index 2ceb245..d920c7f 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
- * Copyright (C) 2014 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9)
+ * Copyright (C) 2015 The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
  */
 package jalview.gui;
 
+import jalview.api.ViewStyleI;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
+import jalview.datamodel.AlignedCodonFrame;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.RnaViewerModel;
+import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.datamodel.StructureViewerModel;
+import jalview.datamodel.StructureViewerModel.StructureData;
+import jalview.ext.varna.RnaModel;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.schemabinding.version2.AlcodMap;
-import jalview.schemabinding.version2.Alcodon;
 import jalview.schemabinding.version2.AlcodonFrame;
 import jalview.schemabinding.version2.Annotation;
 import jalview.schemabinding.version2.AnnotationColours;
@@ -49,6 +57,8 @@ import jalview.schemabinding.version2.OtherData;
 import jalview.schemabinding.version2.PdbentryItem;
 import jalview.schemabinding.version2.Pdbids;
 import jalview.schemabinding.version2.Property;
+import jalview.schemabinding.version2.RnaViewer;
+import jalview.schemabinding.version2.SecondaryStructure;
 import jalview.schemabinding.version2.Sequence;
 import jalview.schemabinding.version2.SequenceSet;
 import jalview.schemabinding.version2.SequenceSetProperties;
@@ -64,7 +74,9 @@ import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.GraduatedColor;
 import jalview.schemes.ResidueColourScheme;
 import jalview.schemes.ResidueProperties;
+import jalview.schemes.UserColourScheme;
 import jalview.structure.StructureSelectionManager;
+import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.util.jarInputStreamProvider;
@@ -94,11 +106,14 @@ 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;
@@ -111,6 +126,7 @@ import javax.swing.JInternalFrame;
 import javax.swing.JOptionPane;
 import javax.swing.SwingUtilities;
 
+import org.exolab.castor.xml.Marshaller;
 import org.exolab.castor.xml.Unmarshaller;
 
 /**
@@ -125,6 +141,44 @@ import org.exolab.castor.xml.Unmarshaller;
  */
 public class Jalview2XML
 {
+  private static final String VIEWER_PREFIX = "viewer_";
+
+  private static final String RNA_PREFIX = "rna_";
+
+  private static final String UTF_8 = "UTF-8";
+
+  // use this with nextCounter() to make unique names for entities
+  private int counter = 0;
+
+  /*
+   * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
+   * of sequence objects are created.
+   */
+  IdentityHashMap<SequenceI, String> seqsToIds = null;
+
+  /**
+   * jalview XML Sequence ID to jalview sequence object reference (both dataset
+   * and alignment sequences. Populated as XML reps of sequence objects are
+   * created.)
+   */
+  Map<String, SequenceI> seqRefIds = null;
+
+  Vector frefedSequence = null;
+
+  boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
+
+  /*
+   * Map of reconstructed AlignFrame objects that appear to have come from
+   * SplitFrame objects (have a dna/protein complement view).
+   */
+  private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<Viewport, AlignFrame>();
+
+  /*
+   * Map from displayed rna structure models to their saved session state jar
+   * entry names
+   */
+  private Map<RnaModel, String> rnaSessions = new HashMap<RnaModel, String>();
+
   /**
    * create/return unique hash string for sq
    * 
@@ -139,7 +193,7 @@ public class Jalview2XML
     }
     if (seqsToIds.containsKey(sq))
     {
-      return (String) seqsToIds.get(sq);
+      return seqsToIds.get(sq);
     }
     else
     {
@@ -180,31 +234,14 @@ public class Jalview2XML
   {
     if (seqsToIds == null)
     {
-      seqsToIds = new IdentityHashMap();
+      seqsToIds = new IdentityHashMap<SequenceI, String>();
     }
     if (seqRefIds == null)
     {
-      seqRefIds = new Hashtable();
+      seqRefIds = new HashMap<String, SequenceI>();
     }
   }
 
-  /**
-   * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
-   * of sequence objects are created.
-   */
-  java.util.IdentityHashMap seqsToIds = null;
-
-  /**
-   * jalview XML Sequence ID to jalview sequence object reference (both dataset
-   * and alignment sequences. Populated as XML reps of sequence objects are
-   * created.)
-   */
-  java.util.Hashtable seqRefIds = null; // key->SequenceI resolution
-
-  Vector frefedSequence = null;
-
-  boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
-
   public Jalview2XML()
   {
   }
@@ -229,7 +266,7 @@ public class Jalview2XML
           {
             if (ref[1] instanceof jalview.datamodel.Mapping)
             {
-              SequenceI seq = (SequenceI) seqRefIds.get(sref);
+              SequenceI seq = seqRefIds.get(sref);
               while (seq.getDatasetSequence() != null)
               {
                 seq = seq.getDatasetSequence();
@@ -240,7 +277,7 @@ public class Jalview2XML
             {
               if (ref[1] instanceof jalview.datamodel.AlignedCodonFrame)
               {
-                SequenceI seq = (SequenceI) seqRefIds.get(sref);
+                SequenceI seq = seqRefIds.get(sref);
                 while (seq.getDatasetSequence() != null)
                 {
                   seq = seq.getDatasetSequence();
@@ -290,28 +327,29 @@ public class Jalview2XML
   }
 
   /**
-   * This maintains a list of viewports, the key being the seqSetId. Important
-   * to set historyItem and redoList for multiple views
+   * This maintains a map of viewports, the key being the seqSetId. Important to
+   * set historyItem and redoList for multiple views
    */
-  Hashtable viewportsAdded;
+  Map<String, AlignViewport> viewportsAdded = new HashMap<String, AlignViewport>();
 
-  Hashtable annotationIds = new Hashtable();
+  Map<String, AlignmentAnnotation> annotationIds = new HashMap<String, AlignmentAnnotation>();
 
   String uniqueSetSuffix = "";
 
   /**
    * List of pdbfiles added to Jar
    */
-  Vector pdbfiles = null;
+  List<String> pdbfiles = null;
 
   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
-  public void SaveState(File statefile)
+  public void saveState(File statefile)
   {
+    FileOutputStream fos = null;
     try
     {
-      FileOutputStream fos = new FileOutputStream(statefile);
+      fos = new FileOutputStream(statefile);
       JarOutputStream jout = new JarOutputStream(fos);
-      SaveState(jout);
+      saveState(jout);
 
     } catch (Exception e)
     {
@@ -327,6 +365,18 @@ public class Jalview2XML
         errorMessage += "(output file was '" + statefile + "')";
       }
       e.printStackTrace();
+    } finally
+    {
+      if (fos != null)
+      {
+        try
+        {
+          fos.close();
+        } catch (IOException e)
+        {
+          // ignore
+        }
+      }
     }
     reportErrors();
   }
@@ -336,9 +386,9 @@ public class Jalview2XML
    * 
    * @param jout
    */
-  public void SaveState(JarOutputStream jout)
+  public void saveState(JarOutputStream jout)
   {
-    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+    AlignFrame[] frames = Desktop.getAlignFrames();
 
     if (frames == null)
     {
@@ -347,80 +397,54 @@ public class Jalview2XML
 
     Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
 
+    /*
+     * ensure cached data is clear before starting
+     */
+    // todo tidy up seqRefIds, seqsToIds initialisation / reset
+    rnaSessions.clear();
+    splitFrameCandidates.clear();
+
     try
     {
 
       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
       // //////////////////////////////////////////////////
-      // NOTE ALSO new PrintWriter must be used for each new JarEntry
-      PrintWriter out = null;
 
-      Vector shortNames = new Vector();
+      List<String> shortNames = new ArrayList<String>();
+      List<String> viewIds = new ArrayList<String>();
 
       // REVERSE ORDER
       for (int i = frames.length - 1; i > -1; i--)
       {
-        if (frames[i] instanceof AlignFrame)
+        AlignFrame af = frames[i];
+        // skip ?
+        if (skipList != null
+                && skipList
+                        .containsKey(af.getViewport().getSequenceSetId()))
         {
-          AlignFrame af = (AlignFrame) frames[i];
-          // skip ?
-          if (skipList != null
-                  && skipList.containsKey(af.getViewport()
-                          .getSequenceSetId()))
-          {
-            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("_"));
-            }
+          continue;
+        }
 
-            shortName = shortName.concat("_" + count);
-            count++;
-          }
+        String shortName = makeFilename(af, shortNames);
 
-          shortNames.addElement(shortName);
+        int ap, apSize = af.alignPanels.size();
 
-          if (!shortName.endsWith(".xml"))
+        for (ap = 0; ap < apSize; ap++)
+        {
+          AlignmentPanel apanel = af.alignPanels.get(ap);
+          String fileName = apSize == 1 ? shortName : ap + shortName;
+          if (!fileName.endsWith(".xml"))
           {
-            shortName = shortName + ".xml";
+            fileName = fileName + ".xml";
           }
 
-          int ap, apSize = af.alignPanels.size();
+          saveState(apanel, fileName, jout, viewIds);
 
-          for (ap = 0; ap < apSize; ap++)
+          String dssid = getDatasetIdRef(af.getViewport().getAlignment()
+                  .getDataset());
+          if (!dsses.containsKey(dssid))
           {
-            AlignmentPanel apanel = (AlignmentPanel) af.alignPanels
-                    .elementAt(ap);
-            String fileName = apSize == 1 ? shortName : ap + shortName;
-            if (!fileName.endsWith(".xml"))
-            {
-              fileName = fileName + ".xml";
-            }
-
-            SaveState(apanel, fileName, jout);
-
-            String dssid = getDatasetIdRef(af.getViewport().getAlignment()
-                    .getDataset());
-            if (!dsses.containsKey(dssid))
-            {
-              dsses.put(dssid, af);
-            }
-
+            dsses.put(dssid, af);
           }
         }
       }
@@ -448,26 +472,69 @@ 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,
+  public boolean saveAlignment(AlignFrame af, String jarFile,
           String fileName)
   {
     try
     {
-      int ap, apSize = af.alignPanels.size();
+      int ap = 0;
+      int apSize = af.alignPanels.size();
       FileOutputStream fos = new FileOutputStream(jarFile);
       JarOutputStream jout = new JarOutputStream(fos);
       Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
-      for (ap = 0; ap < apSize; ap++)
+      List<String> viewIds = new ArrayList<String>();
+
+      for (AlignmentPanel apanel : af.alignPanels)
       {
-        AlignmentPanel apanel = (AlignmentPanel) af.alignPanels
-                .elementAt(ap);
         String jfileName = apSize == 1 ? fileName : fileName + ap;
+        ap++;
         if (!jfileName.endsWith(".xml"))
         {
           jfileName = jfileName + ".xml";
         }
-        SaveState(apanel, jfileName, jout);
+        saveState(apanel, jfileName, jout, viewIds);
         String dssid = getDatasetIdRef(af.getViewport().getAlignment()
                 .getDataset());
         if (!dsses.containsKey(dssid))
@@ -500,17 +567,17 @@ public class Jalview2XML
     for (String dssids : dsses.keySet())
     {
       AlignFrame _af = dsses.get(dssids);
-      String jfileName = MessageManager.formatMessage("label.dataset_for", new String[]{fileName,_af.getTitle()});
+      String jfileName = fileName + " Dataset for " + _af.getTitle();
       if (!jfileName.endsWith(".xml"))
       {
         jfileName = jfileName + ".xml";
       }
-      SaveState(_af.alignPanel, jfileName, true, jout);
+      saveState(_af.alignPanel, jfileName, true, jout, null);
     }
   }
 
   /**
-   * create a JalviewModel from an algnment view and marshall it to a
+   * create a JalviewModel from an alignment view and marshall it to a
    * JarOutputStream
    * 
    * @param ap
@@ -519,17 +586,18 @@ public class Jalview2XML
    *          name of alignment panel written to output stream
    * @param jout
    *          jar output stream
+   * @param viewIds
    * @param out
    *          jar entry name
    */
-  public JalviewModel SaveState(AlignmentPanel ap, String fileName,
-          JarOutputStream jout)
+  public JalviewModel saveState(AlignmentPanel ap, String fileName,
+          JarOutputStream jout, List<String> viewIds)
   {
-    return SaveState(ap, fileName, false, jout);
+    return saveState(ap, fileName, false, jout, viewIds);
   }
 
   /**
-   * create a JalviewModel from an algnment view and marshall it to a
+   * create a JalviewModel from an alignment view and marshall it to a
    * JarOutputStream
    * 
    * @param ap
@@ -544,12 +612,17 @@ public class Jalview2XML
    * @param out
    *          jar entry name
    */
-  public JalviewModel SaveState(AlignmentPanel ap, String fileName,
-          boolean storeDS, JarOutputStream jout)
+  public JalviewModel saveState(AlignmentPanel ap, String fileName,
+          boolean storeDS, JarOutputStream jout, List<String> viewIds)
   {
+    if (viewIds == null)
+    {
+      viewIds = new ArrayList<String>();
+    }
+
     initSeqRefs();
-    Vector jmolViewIds = new Vector(); //
-    Vector userColours = new Vector();
+
+    List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
 
     AlignViewport av = ap.av;
 
@@ -600,14 +673,12 @@ public class Jalview2XML
     Set<String> calcIdSet = new HashSet<String>();
 
     // SAVE SEQUENCES
-    String id = "";
-    jalview.datamodel.SequenceI jds, jdatasq;
     for (int i = 0; i < jal.getHeight(); i++)
     {
-      jds = jal.getSequenceAt(i);
-      jdatasq = jds.getDatasetSequence() == null ? jds : jds
-              .getDatasetSequence();
-      id = seqHash(jds);
+      final SequenceI jds = jal.getSequenceAt(i);
+      final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
+              : jds.getDatasetSequence();
+      String id = seqHash(jds);
 
       if (seqRefIds.get(id) != null)
       {
@@ -663,10 +734,9 @@ public class Jalview2XML
         }
       }
 
-      if (jdatasq.getSequenceFeatures() != null)
+      if (jds.getSequenceFeatures() != null)
       {
-        jalview.datamodel.SequenceFeature[] sf = jdatasq
-                .getSequenceFeatures();
+        jalview.datamodel.SequenceFeature[] sf = jds.getSequenceFeatures();
         int index = 0;
         while (index < sf.length)
         {
@@ -707,90 +777,51 @@ public class Jalview2XML
         }
       }
 
-      if (jdatasq.getPDBId() != null)
+      if (jdatasq.getAllPDBEntries() != null)
       {
-        Enumeration en = jdatasq.getPDBId().elements();
+        Enumeration en = jdatasq.getAllPDBEntries().elements();
         while (en.hasMoreElements())
         {
           Pdbids pdb = new Pdbids();
           jalview.datamodel.PDBEntry entry = (jalview.datamodel.PDBEntry) en
                   .nextElement();
 
-          pdb.setId(entry.getId());
+          String pdbId = entry.getId();
+          pdb.setId(pdbId);
           pdb.setType(entry.getType());
-          //
-          // store any JMol views associated with this seqeunce
-          // this section copes with duplicate entries in the project, so a
-          // dataset only view *should* be coped with sensibly
-          AppJmol jmol;
+
+          /*
+           * Store any structure views associated with this sequence. This
+           * section copes with duplicate entries in the project, so a dataset
+           * only view *should* be coped with sensibly.
+           */
           // This must have been loaded, is it still visible?
           JInternalFrame[] frames = Desktop.desktop.getAllFrames();
           String matchedFile = null;
           for (int f = frames.length - 1; f > -1; f--)
           {
-            if (frames[f] instanceof AppJmol)
+            if (frames[f] instanceof StructureViewerBase)
             {
-              jmol = (AppJmol) frames[f];
-              for (int peid = 0; peid < jmol.jmb.pdbentry.length; peid++)
+              StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
+              matchedFile = saveStructureState(ap, jds, pdb, entry,
+                      viewIds, matchedFile, viewFrame);
+              /*
+               * Only store each structure viewer's state once in the project
+               * jar. First time through only (storeDS==false)
+               */
+              String viewId = viewFrame.getViewId();
+              if (!storeDS && !viewIds.contains(viewId))
               {
-                if (!jmol.jmb.pdbentry[peid].getId().equals(entry.getId())
-                        && !(entry.getId().length() > 4 && entry
-                                .getId()
-                                .toLowerCase()
-                                .startsWith(
-                                        jmol.jmb.pdbentry[peid].getId()
-                                                .toLowerCase())))
-                {
-                  continue;
-                }
-                if (matchedFile == null)
-                {
-                  matchedFile = jmol.jmb.pdbentry[peid].getFile();
-                }
-                else if (!matchedFile.equals(jmol.jmb.pdbentry[peid]
-                        .getFile()))
+                viewIds.add(viewId);
+                try
                 {
-                  Cache.log
-                          .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
-                                  + jmol.jmb.pdbentry[peid].getFile());
-                  ; // record the
-                }
-                // file so we
-                // can get at it if the ID
-                // match is ambiguous (e.g.
-                // 1QIP==1qipA)
-                String statestring = jmol.jmb.viewer.getStateInfo();
-
-                for (int smap = 0; smap < jmol.jmb.sequence[peid].length; smap++)
+                  String viewerState = viewFrame.getStateInfo();
+                  writeJarEntry(jout, getViewerJarEntryName(viewId),
+                          viewerState.getBytes());
+                } catch (IOException e)
                 {
-                  // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
-                  if (jds == jmol.jmb.sequence[peid][smap])
-                  {
-                    StructureState state = new StructureState();
-                    state.setVisible(true);
-                    state.setXpos(jmol.getX());
-                    state.setYpos(jmol.getY());
-                    state.setWidth(jmol.getWidth());
-                    state.setHeight(jmol.getHeight());
-                    state.setViewId(jmol.getViewId());
-                    state.setAlignwithAlignPanel(jmol.isUsedforaligment(ap));
-                    state.setColourwithAlignPanel(jmol
-                            .isUsedforcolourby(ap));
-                    state.setColourByJmol(jmol.isColouredByJmol());
-                    if (!jmolViewIds.contains(state.getViewId()))
-                    {
-                      // Make sure we only store a Jmol state once in each XML
-                      // document.
-                      jmolViewIds.addElement(state.getViewId());
-                      state.setContent(statestring.replaceAll("\n", ""));
-                    }
-                    else
-                    {
-                      state.setContent("# duplicate state");
-                    }
-                    pdb.addStructureState(state);
-                  }
-
+                  System.err.println("Error saving viewer state: "
+                          + e.getMessage());
                 }
               }
             }
@@ -806,37 +837,17 @@ public class Jalview2XML
             pdb.setFile(matchedFile); // entry.getFile());
             if (pdbfiles == null)
             {
-              pdbfiles = new Vector();
+              pdbfiles = new ArrayList<String>();
             }
 
-            if (!pdbfiles.contains(entry.getId()))
+            if (!pdbfiles.contains(pdbId))
             {
-              pdbfiles.addElement(entry.getId());
-              try
-              {
-                File file = new File(matchedFile);
-                if (file.exists() && jout != null)
-                {
-                  byte[] data = new byte[(int) file.length()];
-                  jout.putNextEntry(new JarEntry(entry.getId()));
-                  DataInputStream 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();
-              }
-
+              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();
@@ -856,6 +867,8 @@ public class Jalview2XML
         }
       }
 
+      saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
+
       jms.addJSeq(jseq);
     }
 
@@ -864,31 +877,18 @@ public class Jalview2XML
       jal = av.getAlignment();
     }
     // SAVE MAPPINGS
-    if (jal.getCodonFrames() != null && jal.getCodonFrames().length > 0)
+    if (jal.getCodonFrames() != null)
     {
-      jalview.datamodel.AlignedCodonFrame[] jac = jal.getCodonFrames();
-      for (int i = 0; i < jac.length; i++)
+      Set<AlignedCodonFrame> jac = jal.getCodonFrames();
+      for (AlignedCodonFrame acf : jac)
       {
         AlcodonFrame alc = new AlcodonFrame();
         vamsasSet.addAlcodonFrame(alc);
-        for (int p = 0; p < jac[i].aaWidth; p++)
-        {
-          Alcodon cmap = new Alcodon();
-          if (jac[i].codons[p] != null)
-          {
-            // Null codons indicate a gapped column in the translated peptide
-            // alignment.
-            cmap.setPos1(jac[i].codons[p][0]);
-            cmap.setPos2(jac[i].codons[p][1]);
-            cmap.setPos3(jac[i].codons[p][2]);
-          }
-          alc.addAlcodon(cmap);
-        }
-        if (jac[i].getProtMappings() != null
-                && jac[i].getProtMappings().length > 0)
+        if (acf.getProtMappings() != null
+                && acf.getProtMappings().length > 0)
         {
-          SequenceI[] dnas = jac[i].getdnaSeqs();
-          jalview.datamodel.Mapping[] pmaps = jac[i].getProtMappings();
+          SequenceI[] dnas = acf.getdnaSeqs();
+          jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
           for (int m = 0; m < pmaps.length; m++)
           {
             AlcodMap alcmap = new AlcodMap();
@@ -898,6 +898,37 @@ public class Jalview2XML
             alc.addAlcodMap(alcmap);
           }
         }
+
+        // {
+        // AlcodonFrame alc = new AlcodonFrame();
+        // vamsasSet.addAlcodonFrame(alc);
+        // for (int p = 0; p < acf.aaWidth; p++)
+        // {
+        // Alcodon cmap = new Alcodon();
+        // if (acf.codons[p] != null)
+        // {
+        // // Null codons indicate a gapped column in the translated peptide
+        // // alignment.
+        // cmap.setPos1(acf.codons[p][0]);
+        // cmap.setPos2(acf.codons[p][1]);
+        // cmap.setPos3(acf.codons[p][2]);
+        // }
+        // alc.addAlcodon(cmap);
+        // }
+        // if (acf.getProtMappings() != null
+        // && acf.getProtMappings().length > 0)
+        // {
+        // SequenceI[] dnas = acf.getdnaSeqs();
+        // jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
+        // for (int m = 0; m < pmaps.length; m++)
+        // {
+        // AlcodMap alcmap = new AlcodMap();
+        // alcmap.setDnasq(seqHash(dnas[m]));
+        // alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
+        // false));
+        // alc.addAlcodMap(alcmap);
+        // }
+        // }
       }
     }
 
@@ -945,17 +976,18 @@ public class Jalview2XML
         }
       }
     }
+
     // SAVE ANNOTATIONS
     /**
      * store forward refs from an annotationRow to any groups
      */
-    IdentityHashMap groupRefs = new IdentityHashMap();
+    IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<SequenceGroup, String>();
     if (storeDS)
     {
       for (SequenceI sq : jal.getSequences())
       {
         // Store annotation on dataset sequences only
-        jalview.datamodel.AlignmentAnnotation[] aa = sq.getAnnotation();
+        AlignmentAnnotation[] aa = sq.getAnnotation();
         if (aa != null && aa.length > 0)
         {
           storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
@@ -968,8 +1000,7 @@ public class Jalview2XML
       if (jal.getAlignmentAnnotation() != null)
       {
         // Store the annotation shown on the alignment.
-        jalview.datamodel.AlignmentAnnotation[] aa = jal
-                .getAlignmentAnnotation();
+        AlignmentAnnotation[] aa = jal.getAlignmentAnnotation();
         storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
                 vamsasSet);
       }
@@ -981,70 +1012,66 @@ public class Jalview2XML
       int i = -1;
       for (jalview.datamodel.SequenceGroup sg : jal.getGroups())
       {
-        groups[++i] = new JGroup();
+        JGroup jGroup = new JGroup();
+        groups[++i] = jGroup;
 
-        groups[i].setStart(sg.getStartRes());
-        groups[i].setEnd(sg.getEndRes());
-        groups[i].setName(sg.getName());
+        jGroup.setStart(sg.getStartRes());
+        jGroup.setEnd(sg.getEndRes());
+        jGroup.setName(sg.getName());
         if (groupRefs.containsKey(sg))
         {
-          // group has references so set it's ID field
-          groups[i].setId(groupRefs.get(sg).toString());
+          // group has references so set its ID field
+          jGroup.setId(groupRefs.get(sg));
         }
         if (sg.cs != null)
         {
           if (sg.cs.conservationApplied())
           {
-            groups[i].setConsThreshold(sg.cs.getConservationInc());
+            jGroup.setConsThreshold(sg.cs.getConservationInc());
 
             if (sg.cs instanceof jalview.schemes.UserColourScheme)
             {
-              groups[i].setColour(SetUserColourScheme(sg.cs, userColours,
-                      jms));
+              jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms));
             }
             else
             {
-              groups[i]
-                      .setColour(ColourSchemeProperty.getColourName(sg.cs));
+              jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs));
             }
           }
           else if (sg.cs instanceof jalview.schemes.AnnotationColourGradient)
           {
-            groups[i].setColour("AnnotationColourGradient");
-            groups[i].setAnnotationColours(constructAnnotationColours(
+            jGroup.setColour("AnnotationColourGradient");
+            jGroup.setAnnotationColours(constructAnnotationColours(
                     (jalview.schemes.AnnotationColourGradient) sg.cs,
                     userColours, jms));
           }
           else if (sg.cs instanceof jalview.schemes.UserColourScheme)
           {
-            groups[i]
-                    .setColour(SetUserColourScheme(sg.cs, userColours, jms));
+            jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms));
           }
           else
           {
-            groups[i].setColour(ColourSchemeProperty.getColourName(sg.cs));
+            jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs));
           }
 
-          groups[i].setPidThreshold(sg.cs.getThreshold());
+          jGroup.setPidThreshold(sg.cs.getThreshold());
         }
 
-        groups[i].setOutlineColour(sg.getOutlineColour().getRGB());
-        groups[i].setDisplayBoxes(sg.getDisplayBoxes());
-        groups[i].setDisplayText(sg.getDisplayText());
-        groups[i].setColourText(sg.getColourText());
-        groups[i].setTextCol1(sg.textColour.getRGB());
-        groups[i].setTextCol2(sg.textColour2.getRGB());
-        groups[i].setTextColThreshold(sg.thresholdTextColour);
-        groups[i].setShowUnconserved(sg.getShowNonconserved());
-        groups[i].setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
-        groups[i].setShowConsensusHistogram(sg.isShowConsensusHistogram());
-        groups[i].setShowSequenceLogo(sg.isShowSequenceLogo());
-        groups[i].setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
-        for (int s = 0; s < sg.getSize(); s++)
+        jGroup.setOutlineColour(sg.getOutlineColour().getRGB());
+        jGroup.setDisplayBoxes(sg.getDisplayBoxes());
+        jGroup.setDisplayText(sg.getDisplayText());
+        jGroup.setColourText(sg.getColourText());
+        jGroup.setTextCol1(sg.textColour.getRGB());
+        jGroup.setTextCol2(sg.textColour2.getRGB());
+        jGroup.setTextColThreshold(sg.thresholdTextColour);
+        jGroup.setShowUnconserved(sg.getShowNonconserved());
+        jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
+        jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
+        jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
+        jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
+        for (SequenceI seq : sg.getSequences())
         {
-          jalview.datamodel.Sequence seq = (jalview.datamodel.Sequence) sg
-                  .getSequenceAt(s);
-          groups[i].addSeq(seqHash(seq));
+          jGroup.addSeq(seqHash(seq));
         }
       }
 
@@ -1058,30 +1085,29 @@ public class Jalview2XML
       view.setSequenceSetId(makeHashCode(av.getSequenceSetId(),
               av.getSequenceSetId()));
       view.setId(av.getViewId());
-      view.setViewName(av.viewName);
-      view.setGatheredViews(av.gatherViewsHere);
-
-      if (ap.av.explodedPosition != null)
+      if (av.getCodingComplement() != null)
       {
-        view.setXpos(av.explodedPosition.x);
-        view.setYpos(av.explodedPosition.y);
-        view.setWidth(av.explodedPosition.width);
-        view.setHeight(av.explodedPosition.height);
+        view.setComplementId(av.getCodingComplement().getViewId());
       }
-      else
+      view.setViewName(av.viewName);
+      view.setGatheredViews(av.isGatherViewsHere());
+
+      Rectangle position = ap.av.getExplodedGeometry();
+      if (position == null)
       {
-        view.setXpos(ap.alignFrame.getBounds().x);
-        view.setYpos(ap.alignFrame.getBounds().y);
-        view.setWidth(ap.alignFrame.getBounds().width);
-        view.setHeight(ap.alignFrame.getBounds().height);
+        position = ap.alignFrame.getBounds();
       }
+      view.setXpos(position.x);
+      view.setYpos(position.y);
+      view.setWidth(position.width);
+      view.setHeight(position.height);
 
       view.setStartRes(av.startRes);
       view.setStartSeq(av.startSeq);
 
       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
       {
-        view.setBgColour(SetUserColourScheme(av.getGlobalColourScheme(),
+        view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
                 userColours, jms));
       }
       else if (av.getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
@@ -1109,7 +1135,7 @@ public class Jalview2XML
           view.setConsThreshold(cs.getConservationInc());
           if (cs instanceof jalview.schemes.UserColourScheme)
           {
-            view.setBgColour(SetUserColourScheme(cs, userColours, jms));
+            view.setBgColour(setUserColourScheme(cs, userColours, jms));
           }
         }
 
@@ -1124,35 +1150,37 @@ public class Jalview2XML
       view.setFontName(av.font.getName());
       view.setFontSize(av.font.getSize());
       view.setFontStyle(av.font.getStyle());
-      view.setRenderGaps(av.renderGaps);
-      view.setShowAnnotation(av.getShowAnnotation());
+      view.setScaleProteinAsCdna(av.getViewStyle().isScaleProteinAsCdna());
+      view.setRenderGaps(av.isRenderGaps());
+      view.setShowAnnotation(av.isShowAnnotation());
       view.setShowBoxes(av.getShowBoxes());
       view.setShowColourText(av.getColourText());
       view.setShowFullId(av.getShowJVSuffix());
-      view.setRightAlignIds(av.rightAlignIds);
+      view.setRightAlignIds(av.isRightAlignIds());
       view.setShowSequenceFeatures(av.isShowSequenceFeatures());
       view.setShowText(av.getShowText());
       view.setShowUnconserved(av.getShowUnconserved());
       view.setWrapAlignment(av.getWrapAlignment());
-      view.setTextCol1(av.textColour.getRGB());
-      view.setTextCol2(av.textColour2.getRGB());
-      view.setTextColThreshold(av.thresholdTextColour);
+      view.setTextCol1(av.getTextColour().getRGB());
+      view.setTextCol2(av.getTextColour2().getRGB());
+      view.setTextColThreshold(av.getThresholdTextColour());
       view.setShowConsensusHistogram(av.isShowConsensusHistogram());
       view.setShowSequenceLogo(av.isShowSequenceLogo());
       view.setNormaliseSequenceLogo(av.isNormaliseSequenceLogo());
       view.setShowGroupConsensus(av.isShowGroupConsensus());
       view.setShowGroupConservation(av.isShowGroupConservation());
-      view.setShowNPfeatureTooltip(av.isShowNpFeats());
-      view.setShowDbRefTooltip(av.isShowDbRefs());
-      view.setFollowHighlight(av.followHighlight);
+      view.setShowNPfeatureTooltip(av.isShowNPFeats());
+      view.setShowDbRefTooltip(av.isShowDBRefs());
+      view.setFollowHighlight(av.isFollowHighlight());
       view.setFollowSelection(av.followSelection);
-      view.setIgnoreGapsinConsensus(av.getIgnoreGapsConsensus());
+      view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
       if (av.getFeaturesDisplayed() != null)
       {
         jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings();
 
-        String[] renderOrder = ap.seqPanel.seqCanvas.getFeatureRenderer()
-                .getRenderOrder().toArray(new String[0]);
+        String[] renderOrder = ap.getSeqPanel().seqCanvas
+                .getFeatureRenderer().getRenderOrder()
+                .toArray(new String[0]);
 
         Vector settingsAdded = new Vector();
         Object gstyle = null;
@@ -1161,7 +1189,7 @@ public class Jalview2XML
         {
           for (int ro = 0; ro < renderOrder.length; ro++)
           {
-            gstyle = ap.seqPanel.seqCanvas.getFeatureRenderer()
+            gstyle = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                     .getFeatureStyle(renderOrder[ro]);
             Setting setting = new Setting();
             setting.setType(renderOrder[ro]);
@@ -1179,13 +1207,14 @@ public class Jalview2XML
             }
             else
             {
-              setting.setColour(ap.seqPanel.seqCanvas.getFeatureRenderer()
-                      .getColour(renderOrder[ro]).getRGB());
+              setting.setColour(ap.getSeqPanel().seqCanvas
+                      .getFeatureRenderer().getColour(renderOrder[ro])
+                      .getRGB());
             }
 
             setting.setDisplay(av.getFeaturesDisplayed().isVisible(
                     renderOrder[ro]));
-            float rorder = ap.seqPanel.seqCanvas.getFeatureRenderer()
+            float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                     .getOrder(renderOrder[ro]);
             if (rorder > -1)
             {
@@ -1197,7 +1226,7 @@ public class Jalview2XML
         }
 
         // Make sure we save none displayed feature settings
-        Iterator en = ap.seqPanel.seqCanvas.getFeatureRenderer()
+        Iterator en = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                 .getFeatureColours().keySet().iterator();
         while (en.hasNext())
         {
@@ -1209,11 +1238,11 @@ public class Jalview2XML
 
           Setting setting = new Setting();
           setting.setType(key);
-          setting.setColour(ap.seqPanel.seqCanvas.getFeatureRenderer()
+          setting.setColour(ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                   .getColour(key).getRGB());
 
           setting.setDisplay(false);
-          float rorder = ap.seqPanel.seqCanvas.getFeatureRenderer()
+          float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                   .getOrder(key);
           if (rorder > -1)
           {
@@ -1223,8 +1252,8 @@ public class Jalview2XML
           settingsAdded.addElement(key);
         }
         // is groups actually supposed to be a map here ?
-        en = ap.seqPanel.seqCanvas.getFeatureRenderer().getFeatureGroups()
-                .iterator();
+        en = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
+                .getFeatureGroups().iterator();
         Vector groupsAdded = new Vector();
         while (en.hasNext())
         {
@@ -1235,7 +1264,7 @@ public class Jalview2XML
           }
           Group g = new Group();
           g.setName(grp);
-          g.setDisplay(((Boolean) ap.seqPanel.seqCanvas
+          g.setDisplay(((Boolean) ap.getSeqPanel().seqCanvas
                   .getFeatureRenderer().checkGroupVisibility(grp, false))
                   .booleanValue());
           fs.addGroup(g);
@@ -1257,8 +1286,8 @@ public class Jalview2XML
           for (int c = 0; c < av.getColumnSelection().getHiddenColumns()
                   .size(); c++)
           {
-            int[] region = (int[]) av.getColumnSelection()
-                    .getHiddenColumns().elementAt(c);
+            int[] region = av.getColumnSelection().getHiddenColumns()
+                    .get(c);
             HiddenColumns hc = new HiddenColumns();
             hc.setStart(region[0]);
             hc.setEnd(region[1]);
@@ -1294,12 +1323,12 @@ public class Jalview2XML
       // using save and then load
       try
       {
+        System.out.println("Writing jar entry " + fileName);
         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();
@@ -1312,8 +1341,243 @@ public class Jalview2XML
     return object;
   }
 
+  /**
+   * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
+   * for each viewer, with
+   * <ul>
+   * <li>viewer geometry (position, size, split pane divider location)</li>
+   * <li>index of the selected structure in the viewer (currently shows gapped
+   * or ungapped)</li>
+   * <li>the id of the annotation holding RNA secondary structure</li>
+   * <li>(currently only one SS is shown per viewer, may be more in future)</li>
+   * </ul>
+   * Varna viewer state is also written out (in native Varna XML) to separate
+   * project jar entries. A separate entry is written for each RNA structure
+   * displayed, with the naming convention
+   * <ul>
+   * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
+   * </ul>
+   * 
+   * @param jout
+   * @param jseq
+   * @param jds
+   * @param viewIds
+   * @param ap
+   * @param storeDataset
+   */
+  protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
+          final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
+          boolean storeDataset)
+  {
+    if (Desktop.desktop == null)
+    {
+      return;
+    }
+    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+    for (int f = frames.length - 1; f > -1; f--)
+    {
+      if (frames[f] instanceof AppVarna)
+      {
+        AppVarna varna = (AppVarna) frames[f];
+        /*
+         * link the sequence to every viewer that is showing it and is linked to
+         * its alignment panel
+         */
+        if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
+        {
+          String viewId = varna.getViewId();
+          RnaViewer rna = new RnaViewer();
+          rna.setViewId(viewId);
+          rna.setTitle(varna.getTitle());
+          rna.setXpos(varna.getX());
+          rna.setYpos(varna.getY());
+          rna.setWidth(varna.getWidth());
+          rna.setHeight(varna.getHeight());
+          rna.setDividerLocation(varna.getDividerLocation());
+          rna.setSelectedRna(varna.getSelectedIndex());
+          jseq.addRnaViewer(rna);
+
+          /*
+           * Store each Varna panel's state once in the project per sequence.
+           * First time through only (storeDataset==false)
+           */
+          // boolean storeSessions = false;
+          // String sequenceViewId = viewId + seqsToIds.get(jds);
+          // if (!storeDataset && !viewIds.contains(sequenceViewId))
+          // {
+          // viewIds.add(sequenceViewId);
+          // storeSessions = true;
+          // }
+          for (RnaModel model : varna.getModels())
+          {
+            if (model.seq == jds)
+            {
+              /*
+               * VARNA saves each view (sequence or alignment secondary
+               * structure, gapped or trimmed) as a separate XML file
+               */
+              String jarEntryName = rnaSessions.get(model);
+              if (jarEntryName == null)
+              {
+
+                String varnaStateFile = varna.getStateInfo(model.rna);
+                jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
+                copyFileToJar(jout, varnaStateFile, jarEntryName);
+                rnaSessions.put(model, jarEntryName);
+              }
+              SecondaryStructure ss = new SecondaryStructure();
+              String annotationId = varna.getAnnotation(jds).annotationId;
+              ss.setAnnotationId(annotationId);
+              ss.setViewerState(jarEntryName);
+              ss.setGapped(model.gapped);
+              ss.setTitle(model.title);
+              rna.addSecondaryStructure(ss);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Copy the contents of a file to a new entry added to the output jar
+   * 
+   * @param jout
+   * @param infilePath
+   * @param jarEntryName
+   */
+  protected void copyFileToJar(JarOutputStream jout, String infilePath,
+          String jarEntryName)
+  {
+    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, jarEntryName, 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 jarEntryName
+   * @param data
+   * @throws IOException
+   */
+  protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
+          byte[] data) throws IOException
+  {
+    if (jout != null)
+    {
+      System.out.println("Writing jar entry " + jarEntryName);
+      jout.putNextEntry(new JarEntry(jarEntryName));
+      DataOutputStream dout = new DataOutputStream(jout);
+      dout.write(data, 0, data.length);
+      dout.flush();
+      jout.closeEntry();
+    }
+  }
+
+  /**
+   * Save the state of a structure viewer
+   * 
+   * @param ap
+   * @param jds
+   * @param pdb
+   *          the archive XML element under which to save the state
+   * @param entry
+   * @param viewIds
+   * @param matchedFile
+   * @param viewFrame
+   * @return
+   */
+  protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
+          Pdbids pdb, PDBEntry entry, List<String> viewIds,
+          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);
+      final String pdbId = pdbentry.getId();
+      if (!pdbId.equals(entry.getId())
+              && !(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)
+      {
+        matchedFile = pdbentry.getFile();
+      }
+      else if (!matchedFile.equals(pdbentry.getFile()))
+      {
+        Cache.log
+                .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
+                        + pdbentry.getFile());
+      }
+      // record the
+      // file so we
+      // can get at it if the ID
+      // match is ambiguous (e.g.
+      // 1QIP==1qipA)
+
+      for (int smap = 0; smap < viewFrame.getBinding().getSequence()[peid].length; smap++)
+      {
+        // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
+        if (jds == viewFrame.getBinding().getSequence()[peid][smap])
+        {
+          StructureState state = new StructureState();
+          state.setVisible(true);
+          state.setXpos(viewFrame.getX());
+          state.setYpos(viewFrame.getY());
+          state.setWidth(viewFrame.getWidth());
+          state.setHeight(viewFrame.getHeight());
+          final String viewId = viewFrame.getViewId();
+          state.setViewId(viewId);
+          state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
+          state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
+          state.setColourByJmol(viewFrame.isColouredByViewer());
+          state.setType(viewFrame.getViewerType().toString());
+          pdb.addStructureState(state);
+        }
+      }
+    }
+    return matchedFile;
+  }
+
   private AnnotationColours constructAnnotationColours(
-          AnnotationColourGradient acg, Vector userColours,
+          AnnotationColourGradient acg, List<UserColourScheme> userColours,
           JalviewModelSequence jms)
   {
     AnnotationColours ac = new AnnotationColours();
@@ -1322,7 +1586,7 @@ public class Jalview2XML
     ac.setAnnotation(acg.getAnnotation());
     if (acg.getBaseColour() instanceof jalview.schemes.UserColourScheme)
     {
-      ac.setColourScheme(SetUserColourScheme(acg.getBaseColour(),
+      ac.setColourScheme(setUserColourScheme(acg.getBaseColour(),
               userColours, jms));
     }
     else
@@ -1339,62 +1603,65 @@ public class Jalview2XML
   }
 
   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
-          IdentityHashMap groupRefs, AlignmentViewport av,
-          Set<String> calcIdSet, boolean storeDS, SequenceSet vamsasSet)
+          IdentityHashMap<SequenceGroup, String> groupRefs,
+          AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
+          SequenceSet vamsasSet)
   {
 
     for (int i = 0; i < aa.length; i++)
     {
       Annotation an = new Annotation();
 
-      if (aa[i].annotationId != null)
+      AlignmentAnnotation annotation = aa[i];
+      if (annotation.annotationId != null)
       {
-        annotationIds.put(aa[i].annotationId, aa[i]);
+        annotationIds.put(annotation.annotationId, annotation);
       }
 
-      an.setId(aa[i].annotationId);
+      an.setId(annotation.annotationId);
 
-      an.setVisible(aa[i].visible);
+      an.setVisible(annotation.visible);
 
-      an.setDescription(aa[i].description);
+      an.setDescription(annotation.description);
 
-      if (aa[i].sequenceRef != null)
+      if (annotation.sequenceRef != null)
       {
-        // TODO later annotation sequenceRef should be the XML ID of the
-        // sequence rather than its display name
-        an.setSequenceRef(aa[i].sequenceRef.getName());
+        // 2.9 JAL-1781 xref on sequence id rather than name
+        an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
       }
-      if (aa[i].groupRef != null)
+      if (annotation.groupRef != null)
       {
-        Object groupIdr = groupRefs.get(aa[i].groupRef);
+        String groupIdr = groupRefs.get(annotation.groupRef);
         if (groupIdr == null)
         {
           // make a locally unique String
-          groupRefs.put(aa[i].groupRef,
+          groupRefs.put(
+                  annotation.groupRef,
                   groupIdr = ("" + System.currentTimeMillis()
-                          + aa[i].groupRef.getName() + groupRefs.size()));
+                          + annotation.groupRef.getName() + groupRefs
+                          .size()));
         }
         an.setGroupRef(groupIdr.toString());
       }
 
       // store all visualization attributes for annotation
-      an.setGraphHeight(aa[i].graphHeight);
-      an.setCentreColLabels(aa[i].centreColLabels);
-      an.setScaleColLabels(aa[i].scaleColLabel);
-      an.setShowAllColLabels(aa[i].showAllColLabels);
-      an.setBelowAlignment(aa[i].belowAlignment);
+      an.setGraphHeight(annotation.graphHeight);
+      an.setCentreColLabels(annotation.centreColLabels);
+      an.setScaleColLabels(annotation.scaleColLabel);
+      an.setShowAllColLabels(annotation.showAllColLabels);
+      an.setBelowAlignment(annotation.belowAlignment);
 
-      if (aa[i].graph > 0)
+      if (annotation.graph > 0)
       {
         an.setGraph(true);
-        an.setGraphType(aa[i].graph);
-        an.setGraphGroup(aa[i].graphGroup);
-        if (aa[i].getThreshold() != null)
+        an.setGraphType(annotation.graph);
+        an.setGraphGroup(annotation.graphGroup);
+        if (annotation.getThreshold() != null)
         {
           ThresholdLine line = new ThresholdLine();
-          line.setLabel(aa[i].getThreshold().label);
-          line.setValue(aa[i].getThreshold().value);
-          line.setColour(aa[i].getThreshold().colour.getRGB());
+          line.setLabel(annotation.getThreshold().label);
+          line.setValue(annotation.getThreshold().value);
+          line.setColour(annotation.getThreshold().colour.getRGB());
           an.setThresholdLine(line);
         }
       }
@@ -1403,79 +1670,78 @@ public class Jalview2XML
         an.setGraph(false);
       }
 
-      an.setLabel(aa[i].label);
+      an.setLabel(annotation.label);
 
-      if (aa[i] == av.getAlignmentQualityAnnot()
-              || aa[i] == av.getAlignmentConservationAnnotation()
-              || aa[i] == av.getAlignmentConsensusAnnotation()
-              || aa[i].autoCalculated)
+      if (annotation == av.getAlignmentQualityAnnot()
+              || annotation == av.getAlignmentConservationAnnotation()
+              || annotation == av.getAlignmentConsensusAnnotation()
+              || annotation.autoCalculated)
       {
         // new way of indicating autocalculated annotation -
-        an.setAutoCalculated(aa[i].autoCalculated);
+        an.setAutoCalculated(annotation.autoCalculated);
       }
-      if (aa[i].hasScore())
+      if (annotation.hasScore())
       {
-        an.setScore(aa[i].getScore());
+        an.setScore(annotation.getScore());
       }
 
-      if (aa[i].getCalcId() != null)
+      if (annotation.getCalcId() != null)
       {
-        calcIdSet.add(aa[i].getCalcId());
-        an.setCalcId(aa[i].getCalcId());
+        calcIdSet.add(annotation.getCalcId());
+        an.setCalcId(annotation.getCalcId());
       }
-      if (aa[i].hasProperties())
+      if (annotation.hasProperties())
       {
-        for (String pr : aa[i].getProperties())
+        for (String pr : annotation.getProperties())
         {
           Property prop = new Property();
           prop.setName(pr);
-          prop.setValue(aa[i].getProperty(pr));
+          prop.setValue(annotation.getProperty(pr));
           an.addProperty(prop);
         }
       }
 
       AnnotationElement ae;
-      if (aa[i].annotations != null)
+      if (annotation.annotations != null)
       {
         an.setScoreOnly(false);
-        for (int a = 0; a < aa[i].annotations.length; a++)
+        for (int a = 0; a < annotation.annotations.length; a++)
         {
-          if ((aa[i] == null) || (aa[i].annotations[a] == null))
+          if ((annotation == null) || (annotation.annotations[a] == null))
           {
             continue;
           }
 
           ae = new AnnotationElement();
-          if (aa[i].annotations[a].description != null)
+          if (annotation.annotations[a].description != null)
           {
-            ae.setDescription(aa[i].annotations[a].description);
+            ae.setDescription(annotation.annotations[a].description);
           }
-          if (aa[i].annotations[a].displayCharacter != null)
+          if (annotation.annotations[a].displayCharacter != null)
           {
-            ae.setDisplayCharacter(aa[i].annotations[a].displayCharacter);
+            ae.setDisplayCharacter(annotation.annotations[a].displayCharacter);
           }
 
-          if (!Float.isNaN(aa[i].annotations[a].value))
+          if (!Float.isNaN(annotation.annotations[a].value))
           {
-            ae.setValue(aa[i].annotations[a].value);
+            ae.setValue(annotation.annotations[a].value);
           }
 
           ae.setPosition(a);
-          if (aa[i].annotations[a].secondaryStructure != ' '
-                  && aa[i].annotations[a].secondaryStructure != '\0')
+          if (annotation.annotations[a].secondaryStructure > ' ')
           {
-            ae.setSecondaryStructure(aa[i].annotations[a].secondaryStructure
+            ae.setSecondaryStructure(annotation.annotations[a].secondaryStructure
                     + "");
           }
 
-          if (aa[i].annotations[a].colour != null
-                  && aa[i].annotations[a].colour != java.awt.Color.black)
+          if (annotation.annotations[a].colour != null
+                  && annotation.annotations[a].colour != java.awt.Color.black)
           {
-            ae.setColour(aa[i].annotations[a].colour.getRGB());
+            ae.setColour(annotation.annotations[a].colour.getRGB());
           }
 
           an.addAnnotationElement(ae);
-          if (aa[i].autoCalculated)
+          if (annotation.autoCalculated)
           {
             // only write one non-null entry into the annotation row -
             // sufficient to get the visualization attributes necessary to
@@ -1488,7 +1754,7 @@ public class Jalview2XML
       {
         an.setScoreOnly(true);
       }
-      if (!storeDS || (storeDS && !aa[i].autoCalculated))
+      if (!storeDS || (storeDS && !annotation.autoCalculated))
       {
         // skip autocalculated annotation - these are only provided for
         // alignments
@@ -1587,7 +1853,9 @@ public class Jalview2XML
         return false;
       }
     }
-    throw new Error(MessageManager.formatMessage("error.unsupported_version_calcIdparam", new String[]{calcIdParam.toString()}));
+    throw new Error(MessageManager.formatMessage(
+            "error.unsupported_version_calcIdparam",
+            new Object[] { calcIdParam.toString() }));
   }
 
   /**
@@ -1709,20 +1977,20 @@ public class Jalview2XML
       mp = new Mapping();
 
       jalview.util.MapList mlst = jmp.getMap();
-      int r[] = mlst.getFromRanges();
-      for (int s = 0; s < r.length; s += 2)
+      List<int[]> r = mlst.getFromRanges();
+      for (int[] range : r)
       {
         MapListFrom mfrom = new MapListFrom();
-        mfrom.setStart(r[s]);
-        mfrom.setEnd(r[s + 1]);
+        mfrom.setStart(range[0]);
+        mfrom.setEnd(range[1]);
         mp.addMapListFrom(mfrom);
       }
       r = mlst.getToRanges();
-      for (int s = 0; s < r.length; s += 2)
+      for (int[] range : r)
       {
         MapListTo mto = new MapListTo();
-        mto.setStart(r[s]);
-        mto.setEnd(r[s + 1]);
+        mto.setStart(range[0]);
+        mto.setEnd(range[1]);
         mp.addMapListTo(mto);
       }
       mp.setMapFromUnit(mlst.getFromRatio());
@@ -1768,8 +2036,8 @@ public class Jalview2XML
     return mp;
   }
 
-  String SetUserColourScheme(jalview.schemes.ColourSchemeI cs,
-          Vector userColours, JalviewModelSequence jms)
+  String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
+          List<UserColourScheme> userColours, JalviewModelSequence jms)
   {
     String id = null;
     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
@@ -1814,7 +2082,7 @@ public class Jalview2XML
     return id;
   }
 
-  jalview.schemes.UserColourScheme GetUserColourScheme(
+  jalview.schemes.UserColourScheme getUserColourScheme(
           JalviewModelSequence jms, String id)
   {
     UserColours[] uc = jms.getUserColours();
@@ -1872,7 +2140,7 @@ public class Jalview2XML
    * @param file
    *          - HTTP URL or filename
    */
-  public AlignFrame LoadJalviewAlign(final String file)
+  public AlignFrame loadJalviewAlign(final String file)
   {
 
     jalview.gui.AlignFrame af = null;
@@ -1887,7 +2155,7 @@ public class Jalview2XML
       // so we can re-open the jar input stream for each entry.
 
       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
-      af = LoadJalviewAlign(jprovider);
+      af = loadJalviewAlign(jprovider);
 
     } catch (MalformedURLException e)
     {
@@ -1906,7 +2174,7 @@ public class Jalview2XML
         });
       } catch (Exception x)
       {
-
+        System.err.println("Error loading alignment: " + x.getMessage());
       }
     }
     return af;
@@ -1919,7 +2187,7 @@ public class Jalview2XML
     errorMessage = null;
     uniqueSetSuffix = null;
     seqRefIds = null;
-    viewportsAdded = null;
+    viewportsAdded.clear();
     frefedSequence = null;
 
     if (file.startsWith("http://"))
@@ -1960,7 +2228,7 @@ public class Jalview2XML
    * @param jprovider
    * @return
    */
-  public AlignFrame LoadJalviewAlign(final jarInputStreamProvider jprovider)
+  public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
   {
     errorMessage = null;
     if (uniqueSetSuffix == null)
@@ -1969,19 +2237,15 @@ public class Jalview2XML
     }
     if (seqRefIds == null)
     {
-      seqRefIds = new Hashtable();
-    }
-    if (viewportsAdded == null)
-    {
-      viewportsAdded = new Hashtable();
+      seqRefIds = new HashMap<String, SequenceI>();
     }
     if (frefedSequence == null)
     {
       frefedSequence = new Vector();
     }
 
-    jalview.gui.AlignFrame af = null, _af = null;
-    Hashtable gatherToThisFrame = new Hashtable();
+    AlignFrame af = null, _af = null;
+    Map<String, AlignFrame> gatherToThisFrame = new HashMap<String, AlignFrame>();
     final String file = jprovider.getFilename();
     try
     {
@@ -1999,7 +2263,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);
@@ -2007,11 +2271,11 @@ public class Jalview2XML
           object = (JalviewModel) unmar.unmarshal(in);
           if (true) // !skipViewport(object))
           {
-            _af = LoadFromObject(object, file, true, jprovider);
+            _af = loadFromObject(object, file, true, jprovider);
             if (object.getJalviewModelSequence().getViewportCount() > 0)
             {
               af = _af;
-              if (af.viewport.gatherViewsHere)
+              if (af.viewport.isGatherViewsHere())
               {
                 gatherToThisFrame.put(af.viewport.getSequenceSetId(), af);
               }
@@ -2026,13 +2290,7 @@ public class Jalview2XML
         }
       } while (jarentry != null);
       resolveFrefedSequences();
-    } catch (java.io.FileNotFoundException ex)
-    {
-      ex.printStackTrace();
-      errorMessage = "Couldn't locate Jalview XML file : " + file;
-      System.err.println("Exception whilst loading jalview XML file : "
-              + ex + "\n");
-    } catch (java.net.UnknownHostException ex)
+    } catch (IOException ex)
     {
       ex.printStackTrace();
       errorMessage = "Couldn't locate Jalview XML file : " + file;
@@ -2081,11 +2339,20 @@ public class Jalview2XML
       Desktop.instance.stopLoading();
     }
 
-    Enumeration en = gatherToThisFrame.elements();
-    while (en.hasMoreElements())
+    /*
+     * Regather multiple views (with the same sequence set id) to the frame (if
+     * any) that is flagged as the one to gather to, i.e. convert them to tabbed
+     * views instead of separate frames. Note this doesn't restore a state where
+     * some expanded views in turn have tabbed views - the last "first tab" read
+     * in will play the role of gatherer for all.
+     */
+    for (AlignFrame fr : gatherToThisFrame.values())
     {
-      Desktop.instance.gatherViews((AlignFrame) en.nextElement());
+      Desktop.instance.gatherViews(fr);
     }
+
+    restoreSplitFrames();
+
     if (errorMessage != null)
     {
       reportErrors();
@@ -2094,26 +2361,130 @@ public class Jalview2XML
   }
 
   /**
-   * check errorMessage for a valid error message and raise an error box in the
-   * GUI or write the current errorMessage to stderr and then clear the error
-   * state.
+   * Try to reconstruct and display SplitFrame windows, where each contains
+   * complementary dna and protein alignments. Done by pairing up AlignFrame
+   * objects (created earlier) which have complementary viewport ids associated.
    */
-  protected void reportErrors()
+  protected void restoreSplitFrames()
   {
-    reportErrors(false);
-  }
+    List<SplitFrame> gatherTo = new ArrayList<SplitFrame>();
+    List<AlignFrame> addedToSplitFrames = new ArrayList<AlignFrame>();
+    Map<String, AlignFrame> dna = new HashMap<String, AlignFrame>();
 
-  protected void reportErrors(final boolean saving)
-  {
-    if (errorMessage != null)
+    /*
+     * Identify the DNA alignments
+     */
+    for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
+            .entrySet())
     {
-      final String finalErrorMessage = errorMessage;
-      if (raiseGUI)
+      AlignFrame af = candidate.getValue();
+      if (af.getViewport().getAlignment().isNucleotide())
       {
-        javax.swing.SwingUtilities.invokeLater(new Runnable()
+        dna.put(candidate.getKey().getId(), af);
+      }
+    }
+
+    /*
+     * Try to match up the protein complements
+     */
+    for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
+            .entrySet())
+    {
+      AlignFrame af = candidate.getValue();
+      if (!af.getViewport().getAlignment().isNucleotide())
+      {
+        String complementId = candidate.getKey().getComplementId();
+        // only non-null complements should be in the Map
+        if (complementId != null && dna.containsKey(complementId))
         {
-          @Override
-          public void run()
+          final AlignFrame dnaFrame = dna.get(complementId);
+          SplitFrame sf = createSplitFrame(dnaFrame, af);
+          addedToSplitFrames.add(dnaFrame);
+          addedToSplitFrames.add(af);
+          if (af.viewport.isGatherViewsHere())
+          {
+            gatherTo.add(sf);
+          }
+        }
+      }
+    }
+
+    /*
+     * Open any that we failed to pair up (which shouldn't happen!) as
+     * standalone AlignFrame's.
+     */
+    for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
+            .entrySet())
+    {
+      AlignFrame af = candidate.getValue();
+      if (!addedToSplitFrames.contains(af))
+      {
+        Viewport view = candidate.getKey();
+        Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
+                view.getHeight());
+        System.err.println("Failed to restore view " + view.getTitle()
+                + " to split frame");
+      }
+    }
+
+    /*
+     * Gather back into tabbed views as flagged.
+     */
+    for (SplitFrame sf : gatherTo)
+    {
+      Desktop.instance.gatherViews(sf);
+    }
+
+    splitFrameCandidates.clear();
+  }
+
+  /**
+   * Construct and display one SplitFrame holding DNA and protein alignments.
+   * 
+   * @param dnaFrame
+   * @param proteinFrame
+   * @return
+   */
+  protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
+          AlignFrame proteinFrame)
+  {
+    SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
+    String title = MessageManager.getString("label.linked_view_title");
+    int width = (int) dnaFrame.getBounds().getWidth();
+    int height = (int) (dnaFrame.getBounds().getHeight()
+            + proteinFrame.getBounds().getHeight() + 50);
+    Desktop.addInternalFrame(splitFrame, title, width, height);
+
+    /*
+     * And compute cDNA consensus (couldn't do earlier with consensus as
+     * mappings were not yet present)
+     */
+    proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
+
+    return splitFrame;
+  }
+
+  /**
+   * check errorMessage for a valid error message and raise an error box in the
+   * GUI or write the current errorMessage to stderr and then clear the error
+   * state.
+   */
+  protected void reportErrors()
+  {
+    reportErrors(false);
+  }
+
+  protected void reportErrors(final boolean saving)
+  {
+    if (errorMessage != null)
+    {
+      final String finalErrorMessage = errorMessage;
+      if (raiseGUI)
+      {
+        javax.swing.SwingUtilities.invokeLater(new Runnable()
+        {
+          @Override
+          public void run()
           {
             JOptionPane.showInternalMessageDialog(Desktop.desktop,
                     finalErrorMessage, "Error "
@@ -2130,7 +2501,7 @@ public class Jalview2XML
     errorMessage = null;
   }
 
-  Hashtable<String, String> alreadyLoadedPDB;
+  Map<String, String> alreadyLoadedPDB = new HashMap<String, String>();
 
   /**
    * when set, local views will be updated from view stored in JalviewXML
@@ -2139,17 +2510,47 @@ 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 == null)
+    if (alreadyLoadedPDB.containsKey(pdbId))
     {
-      alreadyLoadedPDB = new Hashtable();
+      return alreadyLoadedPDB.get(pdbId).toString();
     }
 
-    if (alreadyLoadedPDB.containsKey(pdbId))
+    String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb");
+    if (tempFile != null)
     {
-      return alreadyLoadedPDB.get(pdbId).toString();
+      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
     {
@@ -2164,38 +2565,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;
@@ -2233,7 +2642,7 @@ public class Jalview2XML
    *          data source provider
    * @return alignment frame created from view stored in DOM
    */
-  AlignFrame LoadFromObject(JalviewModel object, String file,
+  AlignFrame loadFromObject(JalviewModel object, String file,
           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
   {
     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
@@ -2247,18 +2656,18 @@ public class Jalview2XML
     // ////////////////////////////////
     // LOAD SEQUENCES
 
-    Vector hiddenSeqs = null;
+    List<SequenceI> hiddenSeqs = null;
     jalview.datamodel.Sequence jseq;
 
-    ArrayList tmpseqs = new ArrayList();
+    List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
 
     boolean multipleView = false;
 
-    JSeq[] JSEQ = object.getJalviewModelSequence().getJSeq();
+    JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
     int vi = 0; // counter in vamsasSeq array
-    for (int i = 0; i < JSEQ.length; i++)
+    for (int i = 0; i < jseqs.length; i++)
     {
-      String seqId = JSEQ[i].getId();
+      String seqId = jseqs[i].getId();
 
       if (seqRefIds.get(seqId) != null)
       {
@@ -2270,22 +2679,22 @@ public class Jalview2XML
         jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
                 vamsasSeq[vi].getSequence());
         jseq.setDescription(vamsasSeq[vi].getDescription());
-        jseq.setStart(JSEQ[i].getStart());
-        jseq.setEnd(JSEQ[i].getEnd());
+        jseq.setStart(jseqs[i].getStart());
+        jseq.setEnd(jseqs[i].getEnd());
         jseq.setVamsasId(uniqueSetSuffix + seqId);
         seqRefIds.put(vamsasSeq[vi].getId(), jseq);
         tmpseqs.add(jseq);
         vi++;
       }
 
-      if (JSEQ[i].getHidden())
+      if (jseqs[i].getHidden())
       {
         if (hiddenSeqs == null)
         {
-          hiddenSeqs = new Vector();
+          hiddenSeqs = new ArrayList<SequenceI>();
         }
 
-        hiddenSeqs.addElement(seqRefIds.get(seqId));
+        hiddenSeqs.add(seqRefIds.get(seqId));
       }
 
     }
@@ -2293,13 +2702,10 @@ public class Jalview2XML
     // /
     // Create the alignment object from the sequence set
     // ///////////////////////////////
-    jalview.datamodel.Sequence[] orderedSeqs = new jalview.datamodel.Sequence[tmpseqs
-            .size()];
-
-    tmpseqs.toArray(orderedSeqs);
+    SequenceI[] orderedSeqs = tmpseqs
+            .toArray(new SequenceI[tmpseqs.size()]);
 
-    jalview.datamodel.Alignment al = new jalview.datamodel.Alignment(
-            orderedSeqs);
+    Alignment al = new Alignment(orderedSeqs);
 
     // / Add the alignment properties
     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
@@ -2319,20 +2725,23 @@ public class Jalview2XML
     }
     else
     {
-      recoverDatasetFor(vamsasSet, al);
+      // recover dataset - passing on flag indicating if this a 'viewless'
+      // sequence set (a.k.a. a stored dataset for the project)
+      recoverDatasetFor(vamsasSet, al, object.getJalviewModelSequence()
+              .getViewportCount() == 0);
     }
     // ///////////////////////////////
 
-    Hashtable pdbloaded = new Hashtable();
+    Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
     if (!multipleView)
     {
       // load sequence features, database references and any associated PDB
       // structures for the alignment
       for (int i = 0; i < vamsasSeq.length; i++)
       {
-        if (JSEQ[i].getFeaturesCount() > 0)
+        if (jseqs[i].getFeaturesCount() > 0)
         {
-          Features[] features = JSEQ[i].getFeatures();
+          Features[] features = jseqs[i].getFeatures();
           for (int f = 0; f < features.length; f++)
           {
             jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
@@ -2362,14 +2771,24 @@ public class Jalview2XML
         {
           addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
         }
-        if (JSEQ[i].getPdbidsCount() > 0)
+        if (jseqs[i].getPdbidsCount() > 0)
         {
-          Pdbids[] ids = JSEQ[i].getPdbids();
+          Pdbids[] ids = jseqs[i].getPdbids();
           for (int p = 0; p < ids.length; p++)
           {
             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
             entry.setId(ids[p].getId());
-            entry.setType(ids[p].getType());
+            if (ids[p].getType() != null)
+            {
+              if (ids[p].getType().equalsIgnoreCase("PDB"))
+              {
+                entry.setType(PDBEntry.Type.PDB);
+              }
+              else
+              {
+                entry.setType(PDBEntry.Type.FILE);
+              }
+            }
             if (ids[p].getFile() != null)
             {
               if (!pdbloaded.containsKey(ids[p].getFile()))
@@ -2382,8 +2801,7 @@ public class Jalview2XML
               }
             }
             StructureSelectionManager.getStructureSelectionManager(
-                    Desktop.instance)
-                    .registerPDBEntry(entry);
+                    Desktop.instance).registerPDBEntry(entry);
             al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
           }
         }
@@ -2400,35 +2818,13 @@ public class Jalview2XML
       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
       for (int i = 0; i < alc.length; i++)
       {
-        jalview.datamodel.AlignedCodonFrame cf = new jalview.datamodel.AlignedCodonFrame(
-                alc[i].getAlcodonCount());
-        if (alc[i].getAlcodonCount() > 0)
-        {
-          Alcodon[] alcods = alc[i].getAlcodon();
-          for (int p = 0; p < cf.codons.length; p++)
-          {
-            if (alcods[p].hasPos1() && alcods[p].hasPos2()
-                    && alcods[p].hasPos3())
-            {
-              // translated codons require three valid positions
-              cf.codons[p] = new int[3];
-              cf.codons[p][0] = (int) alcods[p].getPos1();
-              cf.codons[p][1] = (int) alcods[p].getPos2();
-              cf.codons[p][2] = (int) alcods[p].getPos3();
-            }
-            else
-            {
-              cf.codons[p] = null;
-            }
-          }
-        }
+        AlignedCodonFrame cf = new AlignedCodonFrame();
         if (alc[i].getAlcodMapCount() > 0)
         {
           AlcodMap[] maps = alc[i].getAlcodMap();
           for (int m = 0; m < maps.length; m++)
           {
-            SequenceI dnaseq = (SequenceI) seqRefIds
-                    .get(maps[m].getDnasq());
+            SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
             // Load Mapping
             jalview.datamodel.Mapping mapping = null;
             // attach to dna sequence reference.
@@ -2443,23 +2839,23 @@ public class Jalview2XML
             else
             {
               // defer to later
-              frefedSequence.add(new Object[]
-              { maps[m].getDnasq(), cf, mapping });
+              frefedSequence.add(new Object[] { maps[m].getDnasq(), cf,
+                  mapping });
             }
           }
         }
         al.addCodonFrame(cf);
       }
-
     }
 
     // ////////////////////////////////
     // LOAD ANNOTATIONS
-    ArrayList<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
-    /**
+    List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
+
+    /*
      * store any annotations which forward reference a group's ID
      */
-    Hashtable<String, ArrayList<jalview.datamodel.AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, ArrayList<jalview.datamodel.AlignmentAnnotation>>();
+    Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, List<AlignmentAnnotation>>();
 
     if (vamsasSet.getAnnotationCount() > 0)
     {
@@ -2467,40 +2863,42 @@ public class Jalview2XML
 
       for (int i = 0; i < an.length; i++)
       {
+        Annotation annotation = an[i];
+
         /**
          * test if annotation is automatically calculated for this view only
          */
         boolean autoForView = false;
-        if (an[i].getLabel().equals("Quality")
-                || an[i].getLabel().equals("Conservation")
-                || an[i].getLabel().equals("Consensus"))
+        if (annotation.getLabel().equals("Quality")
+                || annotation.getLabel().equals("Conservation")
+                || annotation.getLabel().equals("Consensus"))
         {
           // Kludge for pre 2.5 projects which lacked the autocalculated flag
           autoForView = true;
-          if (!an[i].hasAutoCalculated())
+          if (!annotation.hasAutoCalculated())
           {
-            an[i].setAutoCalculated(true);
+            annotation.setAutoCalculated(true);
           }
         }
         if (autoForView
-                || (an[i].hasAutoCalculated() && an[i].isAutoCalculated()))
+                || (annotation.hasAutoCalculated() && annotation
+                        .isAutoCalculated()))
         {
           // remove ID - we don't recover annotation from other views for
           // view-specific annotation
-          an[i].setId(null);
+          annotation.setId(null);
         }
 
         // set visiblity for other annotation in this view
-        if (an[i].getId() != null
-                && annotationIds.containsKey(an[i].getId()))
+        String annotationId = annotation.getId();
+        if (annotationId != null && annotationIds.containsKey(annotationId))
         {
-          jalview.datamodel.AlignmentAnnotation jda = (jalview.datamodel.AlignmentAnnotation) annotationIds
-                  .get(an[i].getId());
+          AlignmentAnnotation jda = annotationIds.get(annotationId);
           // in principle Visible should always be true for annotation displayed
           // in multiple views
-          if (an[i].hasVisible())
+          if (annotation.hasVisible())
           {
-            jda.visible = an[i].getVisible();
+            jda.visible = annotation.getVisible();
           }
 
           al.addAnnotation(jda);
@@ -2508,11 +2906,11 @@ public class Jalview2XML
           continue;
         }
         // Construct new annotation from model.
-        AnnotationElement[] ae = an[i].getAnnotationElement();
+        AnnotationElement[] ae = annotation.getAnnotationElement();
         jalview.datamodel.Annotation[] anot = null;
         java.awt.Color firstColour = null;
         int anpos;
-        if (!an[i].getScoreOnly())
+        if (!annotation.getScoreOnly())
         {
           anot = new jalview.datamodel.Annotation[al.getWidth()];
           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
@@ -2549,27 +2947,27 @@ public class Jalview2XML
         }
         jalview.datamodel.AlignmentAnnotation jaa = null;
 
-        if (an[i].getGraph())
+        if (annotation.getGraph())
         {
           float llim = 0, hlim = 0;
           // if (autoForView || an[i].isAutoCalculated()) {
           // hlim=11f;
           // }
-          jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
-                  an[i].getDescription(), anot, llim, hlim,
-                  an[i].getGraphType());
+          jaa = new jalview.datamodel.AlignmentAnnotation(
+                  annotation.getLabel(), annotation.getDescription(), anot,
+                  llim, hlim, annotation.getGraphType());
 
-          jaa.graphGroup = an[i].getGraphGroup();
+          jaa.graphGroup = annotation.getGraphGroup();
           jaa._linecolour = firstColour;
-          if (an[i].getThresholdLine() != null)
+          if (annotation.getThresholdLine() != null)
           {
-            jaa.setThreshold(new jalview.datamodel.GraphLine(an[i]
-                    .getThresholdLine().getValue(), an[i]
+            jaa.setThreshold(new jalview.datamodel.GraphLine(annotation
+                    .getThresholdLine().getValue(), annotation
                     .getThresholdLine().getLabel(), new java.awt.Color(
-                    an[i].getThresholdLine().getColour())));
+                    annotation.getThresholdLine().getColour())));
 
           }
-          if (autoForView || an[i].isAutoCalculated())
+          if (autoForView || annotation.isAutoCalculated())
           {
             // Hardwire the symbol display line to ensure that labels for
             // histograms are displayed
@@ -2589,19 +2987,26 @@ public class Jalview2XML
           jaa.annotationId = an[i].getId();
         }
         // recover sequence association
-        if (an[i].getSequenceRef() != null)
+        String sequenceRef = an[i].getSequenceRef();
+        if (sequenceRef != null)
         {
-          if (al.findName(an[i].getSequenceRef()) != null)
+          // from 2.9 sequenceRef is to sequence id (JAL-1781)
+          SequenceI sequence = seqRefIds.get(sequenceRef);
+          if (sequence == null)
+          {
+            // in pre-2.9 projects sequence ref is to sequence name
+            sequence = al.findName(sequenceRef);
+          }
+          if (sequence != null)
           {
-            jaa.createSequenceMapping(al.findName(an[i].getSequenceRef()),
-                    1, true);
-            al.findName(an[i].getSequenceRef()).addAlignmentAnnotation(jaa);
+            jaa.createSequenceMapping(sequence, 1, true);
+            sequence.addAlignmentAnnotation(jaa);
           }
         }
         // and make a note of any group association
         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
         {
-          ArrayList<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
+          List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
                   .get(an[i].getGroupRef());
           if (aal == null)
           {
@@ -2675,38 +3080,37 @@ public class Jalview2XML
       boolean addAnnotSchemeGroup = false;
       for (int i = 0; i < groups.length; i++)
       {
+        JGroup jGroup = groups[i];
         ColourSchemeI cs = null;
-
-        if (groups[i].getColour() != null)
+        if (jGroup.getColour() != null)
         {
-          if (groups[i].getColour().startsWith("ucs"))
+          if (jGroup.getColour().startsWith("ucs"))
           {
-            cs = GetUserColourScheme(jms, groups[i].getColour());
+            cs = getUserColourScheme(jms, jGroup.getColour());
           }
-          else if (groups[i].getColour().equals("AnnotationColourGradient")
-                  && groups[i].getAnnotationColours() != null)
+          else if (jGroup.getColour().equals("AnnotationColourGradient")
+                  && jGroup.getAnnotationColours() != null)
           {
             addAnnotSchemeGroup = true;
             cs = null;
           }
           else
           {
-            cs = ColourSchemeProperty.getColour(al, groups[i].getColour());
+            cs = ColourSchemeProperty.getColour(al, jGroup.getColour());
           }
 
           if (cs != null)
           {
-            cs.setThreshold(groups[i].getPidThreshold(), true);
+            cs.setThreshold(jGroup.getPidThreshold(), true);
           }
         }
 
-        Vector seqs = new Vector();
+        Vector<SequenceI> seqs = new Vector<SequenceI>();
 
-        for (int s = 0; s < groups[i].getSeqCount(); s++)
+        for (int s = 0; s < jGroup.getSeqCount(); s++)
         {
-          String seqId = groups[i].getSeq(s) + "";
-          jalview.datamodel.SequenceI ts = (jalview.datamodel.SequenceI) seqRefIds
-                  .get(seqId);
+          String seqId = jGroup.getSeq(s) + "";
+          SequenceI ts = seqRefIds.get(seqId);
 
           if (ts != null)
           {
@@ -2719,36 +3123,35 @@ public class Jalview2XML
           continue;
         }
 
-        jalview.datamodel.SequenceGroup sg = new jalview.datamodel.SequenceGroup(
-                seqs, groups[i].getName(), cs, groups[i].getDisplayBoxes(),
-                groups[i].getDisplayText(), groups[i].getColourText(),
-                groups[i].getStart(), groups[i].getEnd());
+        SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
+                jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
+                jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd());
 
-        sg.setOutlineColour(new java.awt.Color(groups[i].getOutlineColour()));
+        sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
 
-        sg.textColour = new java.awt.Color(groups[i].getTextCol1());
-        sg.textColour2 = new java.awt.Color(groups[i].getTextCol2());
-        sg.setShowNonconserved(groups[i].hasShowUnconserved() ? groups[i]
+        sg.textColour = new java.awt.Color(jGroup.getTextCol1());
+        sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
+        sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup
                 .isShowUnconserved() : false);
-        sg.thresholdTextColour = groups[i].getTextColThreshold();
-        if (groups[i].hasShowConsensusHistogram())
+        sg.thresholdTextColour = jGroup.getTextColThreshold();
+        if (jGroup.hasShowConsensusHistogram())
         {
-          sg.setShowConsensusHistogram(groups[i].isShowConsensusHistogram());
+          sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
         }
         ;
-        if (groups[i].hasShowSequenceLogo())
+        if (jGroup.hasShowSequenceLogo())
         {
-          sg.setshowSequenceLogo(groups[i].isShowSequenceLogo());
+          sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
         }
-        if (groups[i].hasNormaliseSequenceLogo())
+        if (jGroup.hasNormaliseSequenceLogo())
         {
-          sg.setNormaliseSequenceLogo(groups[i].isNormaliseSequenceLogo());
+          sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
         }
-        if (groups[i].hasIgnoreGapsinConsensus())
+        if (jGroup.hasIgnoreGapsinConsensus())
         {
-          sg.setIgnoreGapsConsensus(groups[i].getIgnoreGapsinConsensus());
+          sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
         }
-        if (groups[i].getConsThreshold() != 0)
+        if (jGroup.getConsThreshold() != 0)
         {
           jalview.analysis.Conservation c = new jalview.analysis.Conservation(
                   "All", ResidueProperties.propHash, 3,
@@ -2758,14 +3161,14 @@ public class Jalview2XML
           sg.cs.setConservation(c);
         }
 
-        if (groups[i].getId() != null && groupAnnotRefs.size() > 0)
+        if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
         {
           // re-instate unique group/annotation row reference
-          ArrayList<jalview.datamodel.AlignmentAnnotation> jaal = groupAnnotRefs
-                  .get(groups[i].getId());
+          List<AlignmentAnnotation> jaal = groupAnnotRefs.get(jGroup
+                  .getId());
           if (jaal != null)
           {
-            for (jalview.datamodel.AlignmentAnnotation jaa : jaal)
+            for (AlignmentAnnotation jaa : jaal)
             {
               jaa.groupRef = sg;
               if (jaa.autoCalculated)
@@ -2790,8 +3193,8 @@ public class Jalview2XML
         if (addAnnotSchemeGroup)
         {
           // reconstruct the annotation colourscheme
-          sg.cs = constructAnnotationColour(
-                  groups[i].getAnnotationColours(), null, al, jms, false);
+          sg.cs = constructAnnotationColour(jGroup.getAnnotationColours(),
+                  null, al, jms, false);
         }
       }
     }
@@ -2877,443 +3280,752 @@ public class Jalview2XML
 
     if (isnewview)
     {
-      af = loadViewport(file, JSEQ, hiddenSeqs, al, jms, view,
+      af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
               uniqueSeqSetId, viewId, autoAlan);
       av = af.viewport;
       ap = af.alignPanel;
     }
-    // LOAD TREES
-    // /////////////////////////////////////
-    if (loadTreesAndStructures && jms.getTreeCount() > 0)
+
+    /*
+     * Load any trees, PDB structures and viewers
+     * 
+     * Not done if flag is false (when this method is used for New View)
+     */
+    if (loadTreesAndStructures)
     {
-      try
+      loadTrees(jms, view, af, av, ap);
+      loadPDBStructures(jprovider, jseqs, af, ap);
+      loadRnaViewers(jprovider, jseqs, ap);
+    }
+    // and finally return.
+    return af;
+  }
+
+  /**
+   * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
+   * panel is restored from separate jar entries, two (gapped and trimmed) per
+   * sequence and secondary structure.
+   * 
+   * Currently each viewer shows just one sequence and structure (gapped and
+   * trimmed), however this method is designed to support multiple sequences or
+   * structures in viewers if wanted in future.
+   * 
+   * @param jprovider
+   * @param jseqs
+   * @param ap
+   */
+  private void loadRnaViewers(jarInputStreamProvider jprovider,
+          JSeq[] jseqs, AlignmentPanel ap)
+  {
+    /*
+     * scan the sequences for references to viewers; create each one the first
+     * time it is referenced, add Rna models to existing viewers
+     */
+    for (JSeq jseq : jseqs)
+    {
+      for (int i = 0; i < jseq.getRnaViewerCount(); i++)
       {
-        for (int t = 0; t < jms.getTreeCount(); t++)
+        RnaViewer viewer = jseq.getRnaViewer(i);
+        AppVarna appVarna = findOrCreateVarnaViewer(viewer,
+                uniqueSetSuffix, ap);
+
+        for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
         {
+          SecondaryStructure ss = viewer.getSecondaryStructure(j);
+          SequenceI seq = seqRefIds.get(jseq.getId());
+          AlignmentAnnotation ann = this.annotationIds.get(ss
+                  .getAnnotationId());
 
-          Tree tree = jms.getTree(t);
+          /*
+           * add the structure to the Varna display (with session state copied
+           * from the jar to a temporary file)
+           */
+          boolean gapped = ss.isGapped();
+          String rnaTitle = ss.getTitle();
+          String sessionState = ss.getViewerState();
+          String tempStateFile = copyJarEntry(jprovider, sessionState,
+                  "varna");
+          RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
+          appVarna.addModelSession(rna, rnaTitle, tempStateFile);
+        }
+        appVarna.setInitialSelection(viewer.getSelectedRna());
+      }
+    }
+  }
 
-          TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
-          if (tp == null)
-          {
-            tp = af.ShowNewickTree(
-                    new jalview.io.NewickFile(tree.getNewick()),
-                    tree.getTitle(), tree.getWidth(), tree.getHeight(),
-                    tree.getXpos(), tree.getYpos());
-            if (tree.getId() != null)
-            {
-              // perhaps bind the tree id to something ?
-            }
-          }
-          else
-          {
-            // update local tree attributes ?
-            // TODO: should check if tp has been manipulated by user - if so its
-            // settings shouldn't be modified
-            tp.setTitle(tree.getTitle());
-            tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
-                    .getWidth(), tree.getHeight()));
-            tp.av = av; // af.viewport; // TODO: verify 'associate with all
-            // views'
-            // works still
-            tp.treeCanvas.av = av; // af.viewport;
-            tp.treeCanvas.ap = ap; // af.alignPanel;
+  /**
+   * Locate and return an already instantiated matching AppVarna, or create one
+   * if not found
+   * 
+   * @param viewer
+   * @param viewIdSuffix
+   * @param ap
+   * @return
+   */
+  protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
+          String viewIdSuffix, AlignmentPanel ap)
+  {
+    /*
+     * on each load a suffix is appended to the saved viewId, to avoid conflicts
+     * if load is repeated
+     */
+    String postLoadId = viewer.getViewId() + viewIdSuffix;
+    for (JInternalFrame frame : getAllFrames())
+    {
+      if (frame instanceof AppVarna)
+      {
+        AppVarna varna = (AppVarna) frame;
+        if (postLoadId.equals(varna.getViewId()))
+        {
+          // this viewer is already instantiated
+          // could in future here add ap as another 'parent' of the
+          // AppVarna window; currently just 1-to-many
+          return varna;
+        }
+      }
+    }
 
-          }
-          if (tp == null)
-          {
-            warn("There was a problem recovering stored Newick tree: \n"
-                    + tree.getNewick());
-            continue;
-          }
+    /*
+     * viewer not found - make it
+     */
+    RnaViewerModel model = new RnaViewerModel(postLoadId,
+            viewer.getTitle(), viewer.getXpos(), viewer.getYpos(),
+            viewer.getWidth(), viewer.getHeight(),
+            viewer.getDividerLocation());
+    AppVarna varna = new AppVarna(model, ap);
 
-          tp.fitToWindow.setState(tree.getFitToWindow());
-          tp.fitToWindow_actionPerformed(null);
+    return varna;
+  }
 
-          if (tree.getFontName() != null)
-          {
-            tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
-                    .getFontStyle(), tree.getFontSize()));
-          }
-          else
+  /**
+   * Load any saved trees
+   * 
+   * @param jms
+   * @param view
+   * @param af
+   * @param av
+   * @param ap
+   */
+  protected void loadTrees(JalviewModelSequence jms, Viewport view,
+          AlignFrame af, AlignViewport av, AlignmentPanel ap)
+  {
+    // TODO result of automated refactoring - are all these parameters needed?
+    try
+    {
+      for (int t = 0; t < jms.getTreeCount(); t++)
+      {
+
+        Tree tree = jms.getTree(t);
+
+        TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
+        if (tp == null)
+        {
+          tp = af.ShowNewickTree(
+                  new jalview.io.NewickFile(tree.getNewick()),
+                  tree.getTitle(), tree.getWidth(), tree.getHeight(),
+                  tree.getXpos(), tree.getYpos());
+          if (tree.getId() != null)
           {
-            tp.setTreeFont(new java.awt.Font(view.getFontName(), view
-                    .getFontStyle(), tree.getFontSize()));
+            // perhaps bind the tree id to something ?
           }
+        }
+        else
+        {
+          // update local tree attributes ?
+          // TODO: should check if tp has been manipulated by user - if so its
+          // settings shouldn't be modified
+          tp.setTitle(tree.getTitle());
+          tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
+                  .getWidth(), tree.getHeight()));
+          tp.av = av; // af.viewport; // TODO: verify 'associate with all
+          // views'
+          // works still
+          tp.treeCanvas.av = av; // af.viewport;
+          tp.treeCanvas.ap = ap; // af.alignPanel;
 
-          tp.showPlaceholders(tree.getMarkUnlinked());
-          tp.showBootstrap(tree.getShowBootstrap());
-          tp.showDistances(tree.getShowDistances());
+        }
+        if (tp == null)
+        {
+          warn("There was a problem recovering stored Newick tree: \n"
+                  + tree.getNewick());
+          continue;
+        }
 
-          tp.treeCanvas.threshold = tree.getThreshold();
+        tp.fitToWindow.setState(tree.getFitToWindow());
+        tp.fitToWindow_actionPerformed(null);
 
-          if (tree.getCurrentTree())
-          {
-            af.viewport.setCurrentTree(tp.getTree());
-          }
+        if (tree.getFontName() != null)
+        {
+          tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
+                  .getFontStyle(), tree.getFontSize()));
+        }
+        else
+        {
+          tp.setTreeFont(new java.awt.Font(view.getFontName(), view
+                  .getFontStyle(), tree.getFontSize()));
         }
 
-      } catch (Exception ex)
-      {
-        ex.printStackTrace();
+        tp.showPlaceholders(tree.getMarkUnlinked());
+        tp.showBootstrap(tree.getShowBootstrap());
+        tp.showDistances(tree.getShowDistances());
+
+        tp.treeCanvas.threshold = tree.getThreshold();
+
+        if (tree.getCurrentTree())
+        {
+          af.viewport.setCurrentTree(tp.getTree());
+        }
       }
-    }
 
-    // //LOAD STRUCTURES
-    if (loadTreesAndStructures)
+    } catch (Exception ex)
     {
-      // run through all PDB ids on the alignment, and collect mappings between
-      // jmol view ids and all sequences referring to it
-      Hashtable<String, Object[]> jmolViewIds = new Hashtable();
+      ex.printStackTrace();
+    }
+  }
 
-      for (int i = 0; i < JSEQ.length; i++)
+  /**
+   * Load and link any saved structure viewers.
+   * 
+   * @param jprovider
+   * @param jseqs
+   * @param af
+   * @param ap
+   */
+  protected void loadPDBStructures(jarInputStreamProvider jprovider,
+          JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
+  {
+    /*
+     * Run through all PDB ids on the alignment, and collect mappings between
+     * distinct view ids and all sequences referring to that view.
+     */
+    Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
+
+    for (int i = 0; i < jseqs.length; i++)
+    {
+      if (jseqs[i].getPdbidsCount() > 0)
       {
-        if (JSEQ[i].getPdbidsCount() > 0)
+        Pdbids[] ids = jseqs[i].getPdbids();
+        for (int p = 0; p < ids.length; p++)
         {
-          Pdbids[] ids = JSEQ[i].getPdbids();
-          for (int p = 0; p < ids.length; p++)
+          final int structureStateCount = ids[p].getStructureStateCount();
+          for (int s = 0; s < structureStateCount; s++)
           {
-            for (int s = 0; s < ids[p].getStructureStateCount(); s++)
+            // check to see if we haven't already created this structure view
+            final StructureState structureState = ids[p]
+                    .getStructureState(s);
+            String sviewid = (structureState.getViewId() == null) ? null
+                    : structureState.getViewId() + uniqueSetSuffix;
+            jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
+            // Originally : ids[p].getFile()
+            // : TODO: verify external PDB file recovery still works in normal
+            // jalview project load
+            jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
+            jpdb.setId(ids[p].getId());
+
+            int x = structureState.getXpos();
+            int y = structureState.getYpos();
+            int width = structureState.getWidth();
+            int height = structureState.getHeight();
+
+            // Probably don't need to do this anymore...
+            // Desktop.desktop.getComponentAt(x, y);
+            // TODO: NOW: check that this recovers the PDB file correctly.
+            String pdbFile = loadPDBFile(jprovider, ids[p].getId());
+            jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
+                    .getId() + "");
+            if (sviewid == null)
             {
-              // check to see if we haven't already created this structure view
-              String sviewid = (ids[p].getStructureState(s).getViewId() == null) ? null
-                      : ids[p].getStructureState(s).getViewId()
-                              + uniqueSetSuffix;
-              jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
-              // Originally : ids[p].getFile()
-              // : TODO: verify external PDB file recovery still works in normal
-              // jalview project load
-              jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
-              jpdb.setId(ids[p].getId());
-
-              int x = ids[p].getStructureState(s).getXpos();
-              int y = ids[p].getStructureState(s).getYpos();
-              int width = ids[p].getStructureState(s).getWidth();
-              int height = ids[p].getStructureState(s).getHeight();
-
-              // Probably don't need to do this anymore...
-              // Desktop.desktop.getComponentAt(x, y);
-              // TODO: NOW: check that this recovers the PDB file correctly.
-              String pdbFile = loadPDBFile(jprovider, ids[p].getId());
-              jalview.datamodel.SequenceI seq = (jalview.datamodel.SequenceI) seqRefIds
-                      .get(JSEQ[i].getId() + "");
-              if (sviewid == null)
-              {
-                sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
-                        + "," + height;
-              }
-              if (!jmolViewIds.containsKey(sviewid))
-              {
-                jmolViewIds.put(sviewid, new Object[]
-                { new int[]
-                { x, y, width, height }, "",
-                    new Hashtable<String, Object[]>(), new boolean[]
-                    { false, false, true } });
-                // Legacy pre-2.7 conversion JAL-823 :
-                // do not assume any view has to be linked for colour by
-                // sequence
-              }
+              sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
+                      + "," + height;
+            }
+            if (!structureViewers.containsKey(sviewid))
+            {
+              structureViewers.put(sviewid,
+                      new StructureViewerModel(x, y, width, height, false,
+                              false, true, structureState.getViewId(),
+                              structureState.getType()));
+              // Legacy pre-2.7 conversion JAL-823 :
+              // do not assume any view has to be linked for colour by
+              // sequence
+            }
 
-              // assemble String[] { pdb files }, String[] { id for each
-              // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
-              // seqs_file 2}, boolean[] {
-              // linkAlignPanel,superposeWithAlignpanel}} from hash
-              Object[] jmoldat = jmolViewIds.get(sviewid);
-              ((boolean[]) jmoldat[3])[0] |= ids[p].getStructureState(s)
-                      .hasAlignwithAlignPanel() ? ids[p].getStructureState(
-                      s).getAlignwithAlignPanel() : false;
-              // never colour by linked panel if not specified
-              ((boolean[]) jmoldat[3])[1] |= ids[p].getStructureState(s)
-                      .hasColourwithAlignPanel() ? ids[p]
-                      .getStructureState(s).getColourwithAlignPanel()
-                      : false;
-              // default for pre-2.7 projects is that Jmol colouring is enabled
-              ((boolean[]) jmoldat[3])[2] &= ids[p].getStructureState(s)
-                      .hasColourByJmol() ? ids[p].getStructureState(s)
-                      .getColourByJmol() : true;
-
-              if (((String) jmoldat[1]).length() < ids[p]
-                      .getStructureState(s).getContent().length())
+            // assemble String[] { pdb files }, String[] { id for each
+            // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
+            // seqs_file 2}, boolean[] {
+            // linkAlignPanel,superposeWithAlignpanel}} from hash
+            StructureViewerModel jmoldat = structureViewers.get(sviewid);
+            jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
+                    | (structureState.hasAlignwithAlignPanel() ? structureState
+                            .getAlignwithAlignPanel() : false));
+
+            /*
+             * Default colour by linked panel to false if not specified (e.g.
+             * for pre-2.7 projects)
+             */
+            boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
+            colourWithAlignPanel |= (structureState
+                    .hasColourwithAlignPanel() ? structureState
+                    .getColourwithAlignPanel() : false);
+            jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
+
+            /*
+             * Default colour by viewer to true if not specified (e.g. for
+             * pre-2.7 projects)
+             */
+            boolean colourByViewer = jmoldat.isColourByViewer();
+            colourByViewer &= structureState.hasColourByJmol() ? structureState
+                    .getColourByJmol() : true;
+            jmoldat.setColourByViewer(colourByViewer);
+
+            if (jmoldat.getStateData().length() < structureState
+                    .getContent().length())
+            {
               {
-                {
-                  jmoldat[1] = ids[p].getStructureState(s).getContent();
-                }
+                jmoldat.setStateData(structureState.getContent());
               }
-              if (ids[p].getFile() != null)
+            }
+            if (ids[p].getFile() != null)
+            {
+              File mapkey = new File(ids[p].getFile());
+              StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
+              if (seqstrmaps == null)
               {
-                File mapkey = new File(ids[p].getFile());
-                Object[] seqstrmaps = (Object[]) ((Hashtable) jmoldat[2])
-                        .get(mapkey);
-                if (seqstrmaps == null)
-                {
-                  ((Hashtable) jmoldat[2]).put(mapkey,
-                          seqstrmaps = new Object[]
-                          { pdbFile, ids[p].getId(), new Vector(),
-                              new Vector() });
-                }
-                if (!((Vector) seqstrmaps[2]).contains(seq))
-                {
-                  ((Vector) seqstrmaps[2]).addElement(seq);
-                  // ((Vector)seqstrmaps[3]).addElement(n) :
-                  // in principle, chains
-                  // should be stored here : do we need to
-                  // TODO: store and recover seq/pdb_id :
-                  // chain mappings
-                }
+                jmoldat.getFileData().put(
+                        mapkey,
+                        seqstrmaps = jmoldat.new StructureData(pdbFile,
+                                ids[p].getId()));
               }
-              else
+              if (!seqstrmaps.getSeqList().contains(seq))
               {
-                errorMessage = ("The Jmol views in this project were imported\nfrom an older version of Jalview.\nPlease review the sequence colour associations\nin the Colour by section of the Jmol View menu.\n\nIn the case of problems, see note at\nhttp://issues.jalview.org/browse/JAL-747");
-                warn(errorMessage);
+                seqstrmaps.getSeqList().add(seq);
+                // TODO and chains?
               }
             }
+            else
+            {
+              errorMessage = ("The Jmol views in this project were imported\nfrom an older version of Jalview.\nPlease review the sequence colour associations\nin the Colour by section of the Jmol View menu.\n\nIn the case of problems, see note at\nhttp://issues.jalview.org/browse/JAL-747");
+              warn(errorMessage);
+            }
           }
         }
       }
+    }
+    // Instantiate the associated structure views
+    for (Entry<String, StructureViewerModel> entry : structureViewers
+            .entrySet())
+    {
+      try
+      {
+        createOrLinkStructureViewer(entry, af, ap, jprovider);
+      } catch (Exception e)
       {
+        System.err.println("Error loading structure viewer: "
+                + e.getMessage());
+        // failed - try the next one
+      }
+    }
+  }
 
-        // Instantiate the associated Jmol views
-        for (Entry<String, Object[]> entry : jmolViewIds.entrySet())
-        {
-          String sviewid = entry.getKey();
-          Object[] svattrib = entry.getValue();
-          int[] geom = (int[]) svattrib[0];
-          String state = (String) svattrib[1];
-          Hashtable<File, Object[]> oldFiles = (Hashtable<File, Object[]>) svattrib[2];
-          final boolean useinJmolsuperpos = ((boolean[]) svattrib[3])[0], usetoColourbyseq = ((boolean[]) svattrib[3])[1], jmolColouring = ((boolean[]) svattrib[3])[2];
-          int x = geom[0], y = geom[1], width = geom[2], height = geom[3];
-          // collate the pdbfile -> sequence mappings from this view
-          Vector<String> pdbfilenames = new Vector<String>();
-          Vector<SequenceI[]> seqmaps = new Vector<SequenceI[]>();
-          Vector<String> pdbids = new Vector<String>();
+  /**
+   * 
+   * @param viewerData
+   * @param af
+   * @param ap
+   * @param jprovider
+   */
+  protected void createOrLinkStructureViewer(
+          Entry<String, StructureViewerModel> viewerData, AlignFrame af,
+          AlignmentPanel ap, jarInputStreamProvider jprovider)
+  {
+    final StructureViewerModel stateData = viewerData.getValue();
 
-          // Search to see if we've already created this Jmol view
-          AppJmol comp = null;
-          JInternalFrame[] frames = null;
-          do
-          {
-            try
-            {
-              frames = Desktop.desktop.getAllFrames();
-            } catch (ArrayIndexOutOfBoundsException e)
-            {
-              // occasional No such child exceptions are thrown here...
-              frames = null;
-              try
-              {
-                Thread.sleep(10);
-              } catch (Exception f)
-              {
-              }
-              ;
-            }
-          } while (frames == null);
-          // search for any Jmol windows already open from other
-          // alignment views that exactly match the stored structure state
-          for (int f = 0; comp == null && f < frames.length; f++)
-          {
-            if (frames[f] instanceof AppJmol)
-            {
-              if (sviewid != null
-                      && ((AppJmol) frames[f]).getViewId().equals(sviewid))
-              {
-                // post jalview 2.4 schema includes structure view id
-                comp = (AppJmol) frames[f];
-              }
-              else if (frames[f].getX() == x && frames[f].getY() == y
-                      && frames[f].getHeight() == height
-                      && frames[f].getWidth() == width)
-              {
-                comp = (AppJmol) frames[f];
-              }
-            }
-          }
+    /*
+     * Search for any viewer windows already open from other alignment views
+     * that exactly match the stored structure state
+     */
+    StructureViewerBase comp = findMatchingViewer(viewerData);
 
-          if (comp == null)
-          {
-            // create a new Jmol window.
-            // First parse the Jmol state to translate filenames loaded into the
-            // view, and record the order in which files are shown in the Jmol
-            // view, so we can add the sequence mappings in same order.
-            StringBuffer newFileLoc = null;
-            int cp = 0, ncp, ecp;
-            while ((ncp = state.indexOf("load ", cp)) > -1)
-            {
-              if (newFileLoc == null)
-              {
-                newFileLoc = new StringBuffer();
-              }
-              do
-              {
-                // look for next filename in load statement
-                newFileLoc.append(state.substring(cp,
-                        ncp = (state.indexOf("\"", ncp + 1) + 1)));
-                String oldfilenam = state.substring(ncp,
-                        ecp = state.indexOf("\"", ncp));
-                // recover the new mapping data for this old filename
-                // have to normalize filename - since Jmol and jalview do
-                // filename
-                // translation differently.
-                Object[] filedat = oldFiles.get(new File(oldfilenam));
-                newFileLoc.append(Platform
-                        .escapeString((String) filedat[0]));
-                pdbfilenames.addElement((String) filedat[0]);
-                pdbids.addElement((String) filedat[1]);
-                seqmaps.addElement(((Vector<SequenceI>) filedat[2])
-                        .toArray(new SequenceI[0]));
-                newFileLoc.append("\"");
-                cp = ecp + 1; // advance beyond last \" and set cursor so we can
-                              // look for next file statement.
-              } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
-            }
-            if (cp > 0)
-            {
-              // just append rest of state
-              newFileLoc.append(state.substring(cp));
-            }
-            else
-            {
-              System.err
-                      .print("Ignoring incomplete Jmol state for PDB ids: ");
-              newFileLoc = new StringBuffer(state);
-              newFileLoc.append("; load append ");
-              for (File id : oldFiles.keySet())
-              {
-                // add this and any other pdb files that should be present in
-                // the viewer
-                Object[] filedat = oldFiles.get(id);
-                String nfilename;
-                newFileLoc.append(((String) filedat[0]));
-                pdbfilenames.addElement((String) filedat[0]);
-                pdbids.addElement((String) filedat[1]);
-                seqmaps.addElement(((Vector<SequenceI>) filedat[2])
-                        .toArray(new SequenceI[0]));
-                newFileLoc.append(" \"");
-                newFileLoc.append((String) filedat[0]);
-                newFileLoc.append("\"");
+    if (comp != null)
+    {
+      linkStructureViewer(ap, comp, stateData);
+      return;
+    }
 
-              }
-              newFileLoc.append(";");
-            }
+    /*
+     * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
+     * "viewer_"+stateData.viewId
+     */
+    if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
+    {
+      createChimeraViewer(viewerData, af, jprovider);
+    }
+    else
+    {
+      /*
+       * else Jmol (if pre-2.9, stateData contains JMOL state string)
+       */
+      createJmolViewer(viewerData, af, jprovider);
+    }
+  }
 
-            if (newFileLoc != null)
-            {
-              int histbug = newFileLoc.indexOf("history = ");
-              histbug += 10;
-              int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";",
-                      histbug);
-              String val = (diff == -1) ? null : newFileLoc.substring(
-                      histbug, diff);
-              if (val != null && val.length() >= 4)
-              {
-                if (val.contains("e"))
-                {
-                  if (val.trim().equals("true"))
-                  {
-                    val = "1";
-                  }
-                  else
-                  {
-                    val = "0";
-                  }
-                  newFileLoc.replace(histbug, diff, val);
-                }
-              }
-              // TODO: assemble String[] { pdb files }, String[] { id for each
-              // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
-              // seqs_file 2}} from hash
-              final String[] pdbf = pdbfilenames
-                      .toArray(new String[pdbfilenames.size()]), id = pdbids
-                      .toArray(new String[pdbids.size()]);
-              final SequenceI[][] sq = seqmaps
-                      .toArray(new SequenceI[seqmaps.size()][]);
-              final String fileloc = newFileLoc.toString(), vid = sviewid;
-              final AlignFrame alf = af;
-              final java.awt.Rectangle rect = new java.awt.Rectangle(x, y,
-                      width, height);
-              try
-              {
-                javax.swing.SwingUtilities.invokeAndWait(new Runnable()
-                {
-                  @Override
-                  public void run()
-                  {
-                    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.Viewer.JMOL, pdbf, id, sq, alf.alignPanel,
-                              useinJmolsuperpos, usetoColourbyseq,
-                              jmolColouring, fileloc, rect, vid);
-                      addNewStructureViewer(sview);
-                    } catch (OutOfMemoryError ex)
-                    {
-                      new OOMWarning("restoring structure view for PDB id "
-                              + id, (OutOfMemoryError) ex.getCause());
-                      if (sview != null && sview.isVisible())
-                      {
-                        sview.closeViewer();
-                        sview.setVisible(false);
-                        sview.dispose();
-                      }
-                    }
-                  }
-                });
-              } catch (InvocationTargetException ex)
-              {
-                warn("Unexpected error when opening Jmol view.", ex);
+  /**
+   * Create a new Chimera viewer.
+   * 
+   * @param data
+   * @param af
+   * @param jprovider
+   */
+  protected void createChimeraViewer(
+          Entry<String, StructureViewerModel> viewerData, AlignFrame af,
+          jarInputStreamProvider jprovider)
+  {
+    StructureViewerModel data = viewerData.getValue();
+    String chimeraSessionFile = data.getStateData();
 
-              } catch (InterruptedException e)
-              {
-                // e.printStackTrace();
-              }
-            }
+    /*
+     * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
+     * 
+     * NB this is the 'saved' viewId as in the project file XML, _not_ the
+     * 'uniquified' sviewid used to reconstruct the viewer here
+     */
+    String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
+    chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
+            "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();
+
+    ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
+            af.alignPanel, pdbArray, seqsArray, colourByChimera,
+            colourBySequence, newViewId);
+    cvf.setSize(data.getWidth(), data.getHeight());
+    cvf.setLocation(data.getX(), data.getY());
+  }
+
+  /**
+   * Create a new Jmol window. First parse the Jmol state to translate filenames
+   * loaded into the view, and record the order in which files are shown in the
+   * Jmol view, so we can add the sequence mappings in same order.
+   * 
+   * @param viewerData
+   * @param af
+   * @param jprovider
+   */
+  protected void createJmolViewer(
+          final Entry<String, StructureViewerModel> viewerData,
+          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
+     */
+    if (ViewerType.JMOL.toString().equals(svattrib.getType()))
+    {
+      state = readJarEntry(jprovider,
+              getViewerJarEntryName(svattrib.getViewId()));
+    }
+
+    List<String> pdbfilenames = new ArrayList<String>();
+    List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
+    List<String> pdbids = new ArrayList<String>();
+    StringBuilder newFileLoc = new StringBuilder(64);
+    int cp = 0, ncp, ecp;
+    Map<File, StructureData> oldFiles = svattrib.getFileData();
+    while ((ncp = state.indexOf("load ", cp)) > -1)
+    {
+      do
+      {
+        // look for next filename in load statement
+        newFileLoc.append(state.substring(cp,
+                ncp = (state.indexOf("\"", ncp + 1) + 1)));
+        String oldfilenam = state.substring(ncp,
+                ecp = state.indexOf("\"", ncp));
+        // recover the new mapping data for this old filename
+        // have to normalize filename - since Jmol and jalview do
+        // filename
+        // translation differently.
+        StructureData filedat = oldFiles.get(new File(oldfilenam));
+        newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
+        pdbfilenames.add(filedat.getFilePath());
+        pdbids.add(filedat.getPdbId());
+        seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
+        newFileLoc.append("\"");
+        cp = ecp + 1; // advance beyond last \" and set cursor so we can
+                      // look for next file statement.
+      } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
+    }
+    if (cp > 0)
+    {
+      // just append rest of state
+      newFileLoc.append(state.substring(cp));
+    }
+    else
+    {
+      System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
+      newFileLoc = new StringBuilder(state);
+      newFileLoc.append("; load append ");
+      for (File id : oldFiles.keySet())
+      {
+        // add this and any other pdb files that should be present in
+        // the viewer
+        StructureData filedat = oldFiles.get(id);
+        newFileLoc.append(filedat.getFilePath());
+        pdbfilenames.add(filedat.getFilePath());
+        pdbids.add(filedat.getPdbId());
+        seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
+        newFileLoc.append(" \"");
+        newFileLoc.append(filedat.getFilePath());
+        newFileLoc.append("\"");
+
+      }
+      newFileLoc.append(";");
+    }
 
+    if (newFileLoc.length() == 0)
+    {
+      return;
+    }
+    int histbug = newFileLoc.indexOf("history = ");
+    if (histbug > -1)
+    {
+      /*
+       * change "history = [true|false];" to "history = [1|0];"
+       */
+      histbug += 10;
+      int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
+      String val = (diff == -1) ? null : newFileLoc
+              .substring(histbug, diff);
+      if (val != null && val.length() >= 4)
+      {
+        if (val.contains("e")) // eh? what can it be?
+        {
+          if (val.trim().equals("true"))
+          {
+            val = "1";
           }
           else
-          // if (comp != null)
           {
-            // NOTE: if the jalview project is part of a shared session then
-            // view synchronization should/could be done here.
+            val = "0";
+          }
+          newFileLoc.replace(histbug, diff, val);
+        }
+      }
+    }
 
-            // add mapping for sequences in this view to an already open Jmol
-            // instance
-            for (File id : oldFiles.keySet())
-            {
-              // add this and any other pdb files that should be present in the
-              // viewer
-              Object[] filedat = oldFiles.get(id);
-              String pdbFile = (String) filedat[0];
-              SequenceI[] seq = ((Vector<SequenceI>) filedat[2])
-                      .toArray(new SequenceI[0]);
-              comp.jmb.ssm.setMapping(seq, null, pdbFile,
-                      jalview.io.AppletFormatAdapter.FILE);
-              comp.jmb.addSequenceForStructFile(pdbFile, seq);
-            }
-            // and add the AlignmentPanel's reference to the Jmol view
-            comp.addAlignmentPanel(ap);
-            if (useinJmolsuperpos)
-            {
-              comp.useAlignmentPanelForSuperposition(ap);
-            }
-            else
-            {
-              comp.excludeAlignmentPanelForSuperposition(ap);
-            }
-            if (usetoColourbyseq)
-            {
-              comp.useAlignmentPanelForColourbyseq(ap, !jmolColouring);
-            }
-            else
+    final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
+            .size()]);
+    final String[] id = pdbids.toArray(new String[pdbids.size()]);
+    final SequenceI[][] sq = seqmaps
+            .toArray(new SequenceI[seqmaps.size()][]);
+    final String fileloc = newFileLoc.toString();
+    final String sviewid = viewerData.getKey();
+    final AlignFrame alf = af;
+    final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
+            svattrib.getWidth(), svattrib.getHeight());
+    try
+    {
+      javax.swing.SwingUtilities.invokeAndWait(new Runnable()
+      {
+        @Override
+        public void run()
+        {
+          JalviewStructureDisplayI sview = null;
+          try
+          {
+            sview = new StructureViewer(alf.alignPanel
+                    .getStructureSelectionManager()).createView(
+                    StructureViewer.ViewerType.JMOL, pdbf, id, sq,
+                    alf.alignPanel, svattrib, fileloc, rect, sviewid);
+            addNewStructureViewer(sview);
+          } catch (OutOfMemoryError ex)
+          {
+            new OOMWarning("restoring structure view for PDB id " + id,
+                    (OutOfMemoryError) ex.getCause());
+            if (sview != null && sview.isVisible())
             {
-              comp.excludeAlignmentPanelForColourbyseq(ap);
+              sview.closeViewer(false);
+              sview.setVisible(false);
+              sview.dispose();
             }
           }
         }
+      });
+    } catch (InvocationTargetException ex)
+    {
+      warn("Unexpected error when opening Jmol view.", ex);
+
+    } catch (InterruptedException e)
+    {
+      // e.printStackTrace();
+    }
+
+  }
+
+  /**
+   * Generates a name for the entry in the project jar file to hold state
+   * information for a structure viewer
+   * 
+   * @param viewId
+   * @return
+   */
+  protected String getViewerJarEntryName(String viewId)
+  {
+    return VIEWER_PREFIX + viewId;
+  }
+
+  /**
+   * Returns any open frame that matches given structure viewer data. The match
+   * is based on the unique viewId, or (for older project versions) the frame's
+   * geometry.
+   * 
+   * @param viewerData
+   * @return
+   */
+  protected StructureViewerBase findMatchingViewer(
+          Entry<String, StructureViewerModel> viewerData)
+  {
+    final String sviewid = viewerData.getKey();
+    final StructureViewerModel svattrib = viewerData.getValue();
+    StructureViewerBase comp = null;
+    JInternalFrame[] frames = getAllFrames();
+    for (JInternalFrame frame : frames)
+    {
+      if (frame instanceof StructureViewerBase)
+      {
+        /*
+         * Post jalview 2.4 schema includes structure view id
+         */
+        if (sviewid != null
+                && ((StructureViewerBase) frame).getViewId()
+                        .equals(sviewid))
+        {
+          comp = (StructureViewerBase) frame;
+          break; // break added in 2.9
+        }
+        /*
+         * Otherwise test for matching position and size of viewer frame
+         */
+        else if (frame.getX() == svattrib.getX()
+                && frame.getY() == svattrib.getY()
+                && frame.getHeight() == svattrib.getHeight()
+                && frame.getWidth() == svattrib.getWidth())
+        {
+          comp = (StructureViewerBase) frame;
+          // no break in faint hope of an exact match on viewId
+        }
       }
     }
-    // and finally return.
-    return af;
+    return comp;
+  }
+
+  /**
+   * Link an AlignmentPanel to an existing structure viewer.
+   * 
+   * @param ap
+   * @param viewer
+   * @param oldFiles
+   * @param useinViewerSuperpos
+   * @param usetoColourbyseq
+   * @param viewerColouring
+   */
+  protected void linkStructureViewer(AlignmentPanel ap,
+          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 = 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
+     */
+    final AAStructureBindingModel binding = viewer.getBinding();
+    for (File id : oldFiles.keySet())
+    {
+      // add this and any other pdb files that should be present in the
+      // viewer
+      StructureData filedat = oldFiles.get(id);
+      String pdbFile = filedat.getFilePath();
+      SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
+      binding.getSsm().setMapping(seq, null, pdbFile,
+              jalview.io.AppletFormatAdapter.FILE);
+      binding.addSequenceForStructFile(pdbFile, seq);
+    }
+    // and add the AlignmentPanel's reference to the view panel
+    viewer.addAlignmentPanel(ap);
+    if (useinViewerSuperpos)
+    {
+      viewer.useAlignmentPanelForSuperposition(ap);
+    }
+    else
+    {
+      viewer.excludeAlignmentPanelForSuperposition(ap);
+    }
+    if (usetoColourbyseq)
+    {
+      viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
+    }
+    else
+    {
+      viewer.excludeAlignmentPanelForColourbyseq(ap);
+    }
+  }
+
+  /**
+   * Get all frames within the Desktop.
+   * 
+   * @return
+   */
+  protected JInternalFrame[] getAllFrames()
+  {
+    JInternalFrame[] frames = null;
+    // TODO is this necessary - is it safe - risk of hanging?
+    do
+    {
+      try
+      {
+        frames = Desktop.desktop.getAllFrames();
+      } catch (ArrayIndexOutOfBoundsException e)
+      {
+        // occasional No such child exceptions are thrown here...
+        try
+        {
+          Thread.sleep(10);
+        } catch (InterruptedException f)
+        {
+        }
+      }
+    } while (frames == null);
+    return frames;
   }
 
   /**
@@ -3325,7 +4037,8 @@ public class Jalview2XML
    * @return true if version is development/null or evaluates to the same or
    *         later X.Y.Z (where X,Y,Z are like [0-9]+b?[0-9]*)
    */
-  private boolean isVersionStringLaterThan(String supported, String version)
+  protected boolean isVersionStringLaterThan(String supported,
+          String version)
   {
     if (version == null || version.equalsIgnoreCase("DEVELOPMENT BUILD")
             || version.equalsIgnoreCase("Test")
@@ -3396,10 +4109,10 @@ public class Jalview2XML
     }
   }
 
-  AlignFrame loadViewport(String file, JSeq[] JSEQ, Vector hiddenSeqs,
-          Alignment al, JalviewModelSequence jms, Viewport view,
-          String uniqueSeqSetId, String viewId,
-          ArrayList<JvAnnotRow> autoAlan)
+  AlignFrame loadViewport(String file, JSeq[] JSEQ,
+          List<SequenceI> hiddenSeqs, Alignment al,
+          JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
+          String viewId, List<JvAnnotRow> autoAlan)
   {
     AlignFrame af = null;
     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
@@ -3413,19 +4126,18 @@ public class Jalview2XML
               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
     }
 
-    af.viewport.gatherViewsHere = view.getGatheredViews();
+    af.viewport.setGatherViewsHere(view.getGatheredViews());
 
     if (view.getSequenceSetId() != null)
     {
-      jalview.gui.AlignViewport av = (jalview.gui.AlignViewport) viewportsAdded
-              .get(uniqueSeqSetId);
+      AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
 
       af.viewport.setSequenceSetId(uniqueSeqSetId);
       if (av != null)
       {
         // propagate shared settings to this new view
-        af.viewport.historyList = av.historyList;
-        af.viewport.redoList = av.redoList;
+        af.viewport.setHistoryList(av.getHistoryList());
+        af.viewport.setRedoList(av.getRedoList());
       }
       else
       {
@@ -3450,14 +4162,17 @@ public class Jalview2XML
         af.viewport.hideRepSequences(al.getSequenceAt(s), hidden);
       }
 
-      jalview.datamodel.SequenceI[] hseqs = new jalview.datamodel.SequenceI[hiddenSeqs
-              .size()];
-
-      for (int s = 0; s < hiddenSeqs.size(); s++)
-      {
-        hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s);
-      }
+      // jalview.datamodel.SequenceI[] hseqs = new
+      // jalview.datamodel.SequenceI[hiddenSeqs
+      // .size()];
+      //
+      // for (int s = 0; s < hiddenSeqs.size(); s++)
+      // {
+      // hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s);
+      // }
 
+      SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
+              .size()]);
       af.viewport.hideSequence(hseqs);
 
     }
@@ -3477,35 +4192,38 @@ public class Jalview2XML
 
     af.viewport.setConservationSelected(view.getConservationSelected());
     af.viewport.setShowJVSuffix(view.getShowFullId());
-    af.viewport.rightAlignIds = view.getRightAlignIds();
-    af.viewport.setFont(new java.awt.Font(view.getFontName(), view
-            .getFontStyle(), view.getFontSize()));
-    af.alignPanel.fontChanged();
+    af.viewport.setRightAlignIds(view.getRightAlignIds());
+    af.viewport.setFont(
+            new java.awt.Font(view.getFontName(), view.getFontStyle(), view
+                    .getFontSize()), true);
+    ViewStyleI vs = af.viewport.getViewStyle();
+    vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
+    af.viewport.setViewStyle(vs);
+    // TODO: allow custom charWidth/Heights to be restored by updating them
+    // after setting font - which means set above to false
     af.viewport.setRenderGaps(view.getRenderGaps());
     af.viewport.setWrapAlignment(view.getWrapAlignment());
-    af.alignPanel.setWrapAlignment(view.getWrapAlignment());
     af.viewport.setShowAnnotation(view.getShowAnnotation());
-    af.alignPanel.setAnnotationVisible(view.getShowAnnotation());
 
     af.viewport.setShowBoxes(view.getShowBoxes());
 
     af.viewport.setShowText(view.getShowText());
 
-    af.viewport.textColour = new java.awt.Color(view.getTextCol1());
-    af.viewport.textColour2 = new java.awt.Color(view.getTextCol2());
-    af.viewport.thresholdTextColour = view.getTextColThreshold();
+    af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
+    af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
+    af.viewport.setThresholdTextColour(view.getTextColThreshold());
     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
             .isShowUnconserved() : false);
     af.viewport.setStartRes(view.getStartRes());
     af.viewport.setStartSeq(view.getStartSeq());
-
+    af.alignPanel.updateLayout();
     ColourSchemeI cs = null;
     // apply colourschemes
     if (view.getBgColour() != null)
     {
       if (view.getBgColour().startsWith("ucs"))
       {
-        cs = GetUserColourScheme(jms, view.getBgColour());
+        cs = getUserColourScheme(jms, view.getBgColour());
       }
       else if (view.getBgColour().startsWith("Annotation"))
       {
@@ -3552,7 +4270,7 @@ public class Jalview2XML
     }
     if (view.hasFollowHighlight())
     {
-      af.viewport.followHighlight = view.getFollowHighlight();
+      af.viewport.setFollowHighlight(view.getFollowHighlight());
     }
     if (view.hasFollowSelection())
     {
@@ -3581,11 +4299,11 @@ public class Jalview2XML
     }
     if (view.hasShowDbRefTooltip())
     {
-      af.viewport.setShowDbRefs(view.getShowDbRefTooltip());
+      af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
     }
     if (view.hasShowNPfeatureTooltip())
     {
-      af.viewport.setShowNpFeats(view.hasShowNPfeatureTooltip());
+      af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
     }
     if (view.hasShowGroupConsensus())
     {
@@ -3674,7 +4392,7 @@ public class Jalview2XML
       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
       FeatureRendererSettings frs = new FeatureRendererSettings(
               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
-      af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer()
+      af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
               .transferSettings(frs);
 
     }
@@ -3706,12 +4424,27 @@ public class Jalview2XML
       }
     }
     af.setMenusFromViewport(af.viewport);
+
     // TODO: we don't need to do this if the viewport is aready visible.
-    Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
-            view.getHeight());
-    af.alignPanel.updateAnnotation(false, true); // recompute any autoannotation
-    reorderAutoannotation(af, al, autoAlan);
-    af.alignPanel.alignmentChanged();
+    /*
+     * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
+     * has a 'cdna/protein complement' view, in which case save it in order to
+     * populate a SplitFrame once all views have been read in.
+     */
+    String complementaryViewId = view.getComplementId();
+    if (complementaryViewId == null)
+    {
+      Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
+              view.getHeight());
+      // recompute any autoannotation
+      af.alignPanel.updateAnnotation(false, true);
+      reorderAutoannotation(af, al, autoAlan);
+      af.alignPanel.alignmentChanged();
+    }
+    else
+    {
+      splitFrameCandidates.put(view, af);
+    }
     return af;
   }
 
@@ -3766,7 +4499,7 @@ public class Jalview2XML
           {
             cs = new AnnotationColourGradient(
                     annAlignment.getAlignmentAnnotation()[i],
-                    GetUserColourScheme(jms,
+                    getUserColourScheme(jms,
                             viewAnnColour.getColourScheme()),
                     viewAnnColour.getAboveThreshold());
           }
@@ -3841,7 +4574,7 @@ public class Jalview2XML
   }
 
   private void reorderAutoannotation(AlignFrame af, Alignment al,
-          ArrayList<JvAnnotRow> autoAlan)
+          List<JvAnnotRow> autoAlan)
   {
     // copy over visualization settings for autocalculated annotation in the
     // view
@@ -3850,8 +4583,8 @@ public class Jalview2XML
       /**
        * Kludge for magic autoannotation names (see JAL-811)
        */
-      String[] magicNames = new String[]
-      { "Consensus", "Quality", "Conservation" };
+      String[] magicNames = new String[] { "Consensus", "Quality",
+          "Conservation" };
       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
       for (String nm : magicNames)
@@ -3865,11 +4598,11 @@ public class Jalview2XML
                         + auan.template.getCalcId()), auan);
       }
       int hSize = al.getAlignmentAnnotation().length;
-      ArrayList<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
+      List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
       // work through any autoCalculated annotation already on the view
       // removing it if it should be placed in a different location on the
       // annotation panel.
-      List<String> remains = new ArrayList(visan.keySet());
+      List<String> remains = new ArrayList<String>(visan.keySet());
       for (int h = 0; h < hSize; h++)
       {
         jalview.datamodel.AlignmentAnnotation jalan = al
@@ -3977,7 +4710,7 @@ public class Jalview2XML
     return false;
   }
 
-  public void AddToSkipList(AlignFrame af)
+  public void addToSkipList(AlignFrame af)
   {
     if (skipList == null)
     {
@@ -3995,7 +4728,8 @@ public class Jalview2XML
     }
   }
 
-  private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al)
+  private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al,
+          boolean ignoreUnrefed)
   {
     jalview.datamodel.Alignment ds = getDatasetFor(vamsasSet.getDatasetId());
     Vector dseqs = null;
@@ -4007,7 +4741,7 @@ public class Jalview2XML
     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
     {
       Sequence vamsasSeq = vamsasSet.getSequence(i);
-      ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs);
+      ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
     }
     // create a new dataset
     if (ds == null)
@@ -4020,7 +4754,7 @@ public class Jalview2XML
       addDatasetRef(vamsasSet.getDatasetId(), ds);
     }
     // set the dataset for the newly imported alignment.
-    if (al.getDataset() == null)
+    if (al.getDataset() == null && !ignoreUnrefed)
     {
       al.setDataset(ds);
     }
@@ -4036,25 +4770,27 @@ public class Jalview2XML
    *          vector to add new dataset sequence to
    */
   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
-          AlignmentI ds, Vector dseqs)
+          AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
   {
     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
     // xRef Codon Maps
-    jalview.datamodel.Sequence sq = (jalview.datamodel.Sequence) seqRefIds
-            .get(vamsasSeq.getId());
-    jalview.datamodel.SequenceI dsq = null;
+    SequenceI sq = seqRefIds.get(vamsasSeq.getId());
+    SequenceI dsq = null;
     if (sq != null && sq.getDatasetSequence() != null)
     {
       dsq = sq.getDatasetSequence();
     }
-
+    if (sq == null && ignoreUnrefed)
+    {
+      return;
+    }
     String sqid = vamsasSeq.getDsseqid();
     if (dsq == null)
     {
       // need to create or add a new dataset sequence reference to this sequence
       if (sqid != null)
       {
-        dsq = (jalview.datamodel.SequenceI) seqRefIds.get(sqid);
+        dsq = seqRefIds.get(sqid);
       }
       // check again
       if (dsq == null)
@@ -4114,7 +4850,7 @@ public class Jalview2XML
     // if (pre || post)
     if (sq != dsq)
     {
-      StringBuffer sb = new StringBuffer();
+      // StringBuffer sb = new StringBuffer();
       String newres = jalview.analysis.AlignSeq.extractGaps(
               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
@@ -4141,20 +4877,24 @@ public class Jalview2XML
     }
   }
 
-  java.util.Hashtable datasetIds = null;
+  /*
+   * TODO use AlignmentI here and in related methods - needs
+   * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
+   */
+  Hashtable<String, Alignment> datasetIds = null;
 
-  java.util.IdentityHashMap dataset2Ids = null;
+  IdentityHashMap<Alignment, String> dataset2Ids = null;
 
   private Alignment getDatasetFor(String datasetId)
   {
     if (datasetIds == null)
     {
-      datasetIds = new Hashtable();
+      datasetIds = new Hashtable<String, Alignment>();
       return null;
     }
     if (datasetIds.containsKey(datasetId))
     {
-      return (Alignment) datasetIds.get(datasetId);
+      return datasetIds.get(datasetId);
     }
     return null;
   }
@@ -4163,7 +4903,7 @@ public class Jalview2XML
   {
     if (datasetIds == null)
     {
-      datasetIds = new Hashtable();
+      datasetIds = new Hashtable<String, Alignment>();
     }
     datasetIds.put(datasetId, dataset);
   }
@@ -4174,7 +4914,7 @@ public class Jalview2XML
    * @param dataset
    * @return
    */
-  private String getDatasetIdRef(jalview.datamodel.Alignment dataset)
+  private String getDatasetIdRef(Alignment dataset)
   {
     if (dataset.getDataset() != null)
     {
@@ -4186,11 +4926,11 @@ public class Jalview2XML
       // make a new datasetId and record it
       if (dataset2Ids == null)
       {
-        dataset2Ids = new IdentityHashMap();
+        dataset2Ids = new IdentityHashMap<Alignment, String>();
       }
       else
       {
-        datasetId = (String) dataset2Ids.get(dataset);
+        datasetId = dataset2Ids.get(dataset);
       }
       if (datasetId == null)
       {
@@ -4250,12 +4990,11 @@ public class Jalview2XML
           /**
            * recover from hash
            */
-          jmap.setTo((SequenceI) seqRefIds.get(dsfor));
+          jmap.setTo(seqRefIds.get(dsfor));
         }
         else
         {
-          frefedSequence.add(new Object[]
-          { dsfor, jmap });
+          frefedSequence.add(new Object[] { dsfor, jmap });
         }
       }
       else
@@ -4264,14 +5003,14 @@ public class Jalview2XML
          * local sequence definition
          */
         Sequence ms = mc.getSequence();
-        jalview.datamodel.Sequence djs = null;
+        SequenceI djs = null;
         String sqid = ms.getDsseqid();
         if (sqid != null && sqid.length() > 0)
         {
           /*
            * recover dataset sequence
            */
-          djs = (jalview.datamodel.Sequence) seqRefIds.get(sqid);
+          djs = seqRefIds.get(sqid);
         }
         else
         {
@@ -4309,8 +5048,7 @@ public class Jalview2XML
           boolean keepSeqRefs)
   {
     initSeqRefs();
-    jalview.schemabinding.version2.JalviewModel jm = SaveState(ap, null,
-            null);
+    JalviewModel jm = saveState(ap, null, null, null);
 
     if (!keepSeqRefs)
     {
@@ -4330,9 +5068,9 @@ public class Jalview2XML
       frefedSequence = new Vector();
     }
 
-    viewportsAdded = new Hashtable();
+    viewportsAdded.clear();
 
-    AlignFrame af = LoadFromObject(jm, null, false, null);
+    AlignFrame af = loadFromObject(jm, null, false, null);
     af.alignPanels.clear();
     af.closeMenuItem_actionPerformed(true);
 
@@ -4466,24 +5204,20 @@ public class Jalview2XML
         // register sequence object so the XML parser can recover it.
         if (seqRefIds == null)
         {
-          seqRefIds = new Hashtable();
+          seqRefIds = new HashMap<String, SequenceI>();
         }
         if (seqsToIds == null)
         {
-          seqsToIds = new IdentityHashMap();
+          seqsToIds = new IdentityHashMap<SequenceI, String>();
         }
-        seqRefIds.put(jv2vobj.get(jvobj).toString(), jvobj);
-        seqsToIds.put(jvobj, id);
+        seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
+        seqsToIds.put((SequenceI) jvobj, id);
       }
       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
       {
-        if (annotationIds == null)
-        {
-          annotationIds = new Hashtable();
-        }
         String anid;
-        annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvobj);
-        jalview.datamodel.AlignmentAnnotation jvann = (jalview.datamodel.AlignmentAnnotation) jvobj;
+        AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
+        annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
         if (jvann.annotationId == null)
         {
           jvann.annotationId = anid;
@@ -4534,4 +5268,77 @@ 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;
+  }
+
+  /**
+   * Returns an incrementing counter (0, 1, 2...)
+   * 
+   * @return
+   */
+  private synchronized int nextCounter()
+  {
+    return counter++;
+  }
 }