JAL-1645 Version-Rel Version 2.9 Year-Rel 2015 Licensing glob
[jalview.git] / src / jalview / gui / Jalview2XML.java
index 728d2fc..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.ViewerData;
-import jalview.datamodel.ViewerData.StructureData;
+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;
@@ -52,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;
@@ -74,6 +81,8 @@ import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.util.jarInputStreamProvider;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
+import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.dm.AAConSettings;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
@@ -117,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;
 
 /**
@@ -131,6 +141,15 @@ 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.
@@ -148,6 +167,18 @@ public class Jalview2XML
 
   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
    * 
@@ -296,12 +327,12 @@ 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 = "";
 
@@ -357,7 +388,7 @@ public class Jalview2XML
    */
   public void saveState(JarOutputStream jout)
   {
-    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+    AlignFrame[] frames = Desktop.getAlignFrames();
 
     if (frames == null)
     {
@@ -366,78 +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
       // //////////////////////////////////////////////////
 
-      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();
+          continue;
+        }
 
-          if (shortName.indexOf(File.separatorChar) > -1)
-          {
-            shortName = shortName.substring(shortName
-                    .lastIndexOf(File.separatorChar) + 1);
-          }
+        String shortName = makeFilename(af, shortNames);
 
-          int count = 1;
+        int ap, apSize = af.alignPanels.size();
 
-          while (shortNames.contains(shortName))
+        for (ap = 0; ap < apSize; ap++)
+        {
+          AlignmentPanel apanel = af.alignPanels.get(ap);
+          String fileName = apSize == 1 ? shortName : ap + shortName;
+          if (!fileName.endsWith(".xml"))
           {
-            if (shortName.endsWith("_" + (count - 1)))
-            {
-              shortName = shortName
-                      .substring(0, shortName.lastIndexOf("_"));
-            }
-
-            shortName = shortName.concat("_" + count);
-            count++;
+            fileName = fileName + ".xml";
           }
 
-          shortNames.addElement(shortName);
+          saveState(apanel, fileName, jout, viewIds);
 
-          if (!shortName.endsWith(".xml"))
+          String dssid = getDatasetIdRef(af.getViewport().getAlignment()
+                  .getDataset());
+          if (!dsses.containsKey(dssid))
           {
-            shortName = shortName + ".xml";
-          }
-
-          int ap, apSize = af.alignPanels.size();
-
-          for (ap = 0; ap < apSize; ap++)
-          {
-            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);
           }
         }
       }
@@ -465,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,
           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))
@@ -522,7 +572,7 @@ public class Jalview2XML
       {
         jfileName = jfileName + ".xml";
       }
-      saveState(_af.alignPanel, jfileName, true, jout);
+      saveState(_af.alignPanel, jfileName, true, jout, null);
     }
   }
 
@@ -536,13 +586,14 @@ 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)
+          JarOutputStream jout, List<String> viewIds)
   {
-    return saveState(ap, fileName, false, jout);
+    return saveState(ap, fileName, false, jout, viewIds);
   }
 
   /**
@@ -562,10 +613,15 @@ public class Jalview2XML
    *          jar entry name
    */
   public JalviewModel saveState(AlignmentPanel ap, String fileName,
-          boolean storeDS, JarOutputStream jout)
+          boolean storeDS, JarOutputStream jout, List<String> viewIds)
   {
+    if (viewIds == null)
+    {
+      viewIds = new ArrayList<String>();
+    }
+
     initSeqRefs();
-    List<String> viewIds = new ArrayList<String>();
+
     List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
 
     AlignViewport av = ap.av;
@@ -617,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)
       {
@@ -680,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)
         {
@@ -724,16 +777,17 @@ 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());
 
           /*
@@ -751,6 +805,25 @@ public class Jalview2XML
               StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
               matchedFile = saveStructureState(ap, jds, pdb, entry,
                       viewIds, matchedFile, viewFrame);
+              /*
+               * Only store each structure viewer's state once in the project
+               * jar. First time through only (storeDS==false)
+               */
+              String viewId = viewFrame.getViewId();
+              if (!storeDS && !viewIds.contains(viewId))
+              {
+                viewIds.add(viewId);
+                try
+                {
+                  String viewerState = viewFrame.getStateInfo();
+                  writeJarEntry(jout, getViewerJarEntryName(viewId),
+                          viewerState.getBytes());
+                } catch (IOException e)
+                {
+                  System.err.println("Error saving viewer state: "
+                          + e.getMessage());
+                }
+              }
             }
           }
 
@@ -767,47 +840,14 @@ public class Jalview2XML
               pdbfiles = new ArrayList<String>();
             }
 
-            if (!pdbfiles.contains(entry.getId()))
+            if (!pdbfiles.contains(pdbId))
             {
-              pdbfiles.add(entry.getId());
-              DataInputStream dis = null;
-              try
-              {
-                File file = new File(matchedFile);
-                if (file.exists() && jout != null)
-                {
-                  byte[] data = new byte[(int) file.length()];
-                  jout.putNextEntry(new JarEntry(entry.getId()));
-                  dis = new DataInputStream(
-                          new FileInputStream(file));
-                  dis.readFully(data);
-
-                  DataOutputStream dout = new DataOutputStream(jout);
-                  dout.write(data, 0, data.length);
-                  dout.flush();
-                  jout.closeEntry();
-                }
-              } catch (Exception ex)
-              {
-                ex.printStackTrace();
-              } finally
-              {
-                if (dis != null)
-                {
-                  try
-                  {
-                    dis.close();
-                  } catch (IOException e)
-                  {
-                    // ignore
-                  }
-                }
-              }
-
+              pdbfiles.add(pdbId);
+              copyFileToJar(jout, matchedFile, pdbId);
             }
           }
 
-          if (entry.getProperty() != null)
+          if (entry.getProperty() != null && !entry.getProperty().isEmpty())
           {
             PdbentryItem item = new PdbentryItem();
             Hashtable properties = entry.getProperty();
@@ -827,6 +867,8 @@ public class Jalview2XML
         }
       }
 
+      saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
+
       jms.addJSeq(jseq);
     }
 
@@ -835,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++)
+        if (acf.getProtMappings() != null
+                && acf.getProtMappings().length > 0)
         {
-          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)
-        {
-          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();
@@ -869,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);
+        // }
+        // }
       }
     }
 
@@ -916,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,
@@ -939,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);
       }
@@ -952,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));
         }
       }
 
@@ -1029,23 +1085,22 @@ 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);
@@ -1095,34 +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.isRightAlignIds());
-      view.setShowSequenceFeatures(av.showSequenceFeatures);
+      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.getSeqPanel().seqCanvas.getFeatureRenderer().renderOrder;
+        String[] renderOrder = ap.getSeqPanel().seqCanvas
+                .getFeatureRenderer().getRenderOrder()
+                .toArray(new String[0]);
 
         Vector settingsAdded = new Vector();
         Object gstyle = null;
@@ -1149,12 +1207,13 @@ public class Jalview2XML
             }
             else
             {
-              setting.setColour(ap.getSeqPanel().seqCanvas.getFeatureRenderer()
-                      .getColour(renderOrder[ro]).getRGB());
+              setting.setColour(ap.getSeqPanel().seqCanvas
+                      .getFeatureRenderer().getColour(renderOrder[ro])
+                      .getRGB());
             }
 
-            setting.setDisplay(av.getFeaturesDisplayed()
-                    .containsKey(renderOrder[ro]));
+            setting.setDisplay(av.getFeaturesDisplayed().isVisible(
+                    renderOrder[ro]));
             float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                     .getOrder(renderOrder[ro]);
             if (rorder > -1)
@@ -1167,8 +1226,8 @@ public class Jalview2XML
         }
 
         // Make sure we save none displayed feature settings
-        Iterator en = ap.getSeqPanel().seqCanvas.getFeatureRenderer().featureColours
-                .keySet().iterator();
+        Iterator en = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
+                .getFeatureColours().keySet().iterator();
         while (en.hasNext())
         {
           String key = en.next().toString();
@@ -1192,8 +1251,9 @@ public class Jalview2XML
           fs.addSetting(setting);
           settingsAdded.addElement(key);
         }
-        en = ap.getSeqPanel().seqCanvas.getFeatureRenderer().featureGroups
-                .keySet().iterator();
+        // is groups actually supposed to be a map here ?
+        en = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
+                .getFeatureGroups().iterator();
         Vector groupsAdded = new Vector();
         while (en.hasNext())
         {
@@ -1205,7 +1265,7 @@ public class Jalview2XML
           Group g = new Group();
           g.setName(grp);
           g.setDisplay(((Boolean) ap.getSeqPanel().seqCanvas
-                  .getFeatureRenderer().featureGroups.get(grp))
+                  .getFeatureRenderer().checkGroupVisibility(grp, false))
                   .booleanValue());
           fs.addGroup(g);
           groupsAdded.addElement(grp);
@@ -1226,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]);
@@ -1263,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();
@@ -1282,6 +1342,165 @@ public class Jalview2XML
   }
 
   /**
+   * 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
@@ -1298,26 +1517,30 @@ public class Jalview2XML
           Pdbids pdb, PDBEntry entry, List<String> viewIds,
           String matchedFile, StructureViewerBase viewFrame)
   {
-    final AAStructureBindingModel bindingModel = viewFrame
-            .getBinding();
-    for (int peid = 0; peid < bindingModel
-            .getPdbCount(); peid++)
+    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())))
+                      .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()))
+      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): "
@@ -1328,10 +1551,8 @@ public class Jalview2XML
       // can get at it if the ID
       // match is ambiguous (e.g.
       // 1QIP==1qipA)
-      String statestring = viewFrame.getStateInfo();
 
-      for (int smap = 0; smap < viewFrame.getBinding()
-              .getSequence()[peid].length; smap++)
+      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])
@@ -1345,21 +1566,9 @@ public class Jalview2XML
           final String viewId = viewFrame.getViewId();
           state.setViewId(viewId);
           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
-          state.setColourwithAlignPanel(viewFrame
-                  .isUsedforcolourby(ap));
+          state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
           state.setColourByJmol(viewFrame.isColouredByViewer());
-          /*
-           * Only store each structure viewer's state once in each XML document.
-           */
-          if (!viewIds.contains(viewId))
-          {
-            viewIds.add(viewId);
-            state.setContent(statestring.replaceAll("\n", ""));
-          }
-          else
-          {
-            state.setContent("# duplicate state");
-          }
+          state.setType(viewFrame.getViewerType().toString());
           pdb.addStructureState(state);
         }
       }
@@ -1394,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);
         }
       }
@@ -1458,77 +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 > ' ')
+          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
@@ -1541,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
@@ -1640,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() }));
   }
 
   /**
@@ -1762,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());
@@ -1959,7 +2174,7 @@ public class Jalview2XML
         });
       } catch (Exception x)
       {
-
+        System.err.println("Error loading alignment: " + x.getMessage());
       }
     }
     return af;
@@ -1972,7 +2187,7 @@ public class Jalview2XML
     errorMessage = null;
     uniqueSetSuffix = null;
     seqRefIds = null;
-    viewportsAdded = null;
+    viewportsAdded.clear();
     frefedSequence = null;
 
     if (file.startsWith("http://"))
@@ -2024,17 +2239,13 @@ public class Jalview2XML
     {
       seqRefIds = new HashMap<String, SequenceI>();
     }
-    if (viewportsAdded == null)
-    {
-      viewportsAdded = new Hashtable();
-    }
     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
     {
@@ -2052,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);
@@ -2064,7 +2275,7 @@ public class Jalview2XML
             if (object.getJalviewModelSequence().getViewportCount() > 0)
             {
               af = _af;
-              if (af.viewport.gatherViewsHere)
+              if (af.viewport.isGatherViewsHere())
               {
                 gatherToThisFrame.put(af.viewport.getSequenceSetId(), af);
               }
@@ -2079,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;
@@ -2134,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();
@@ -2147,6 +2361,110 @@ public class Jalview2XML
   }
 
   /**
+   * 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 restoreSplitFrames()
+  {
+    List<SplitFrame> gatherTo = new ArrayList<SplitFrame>();
+    List<AlignFrame> addedToSplitFrames = new ArrayList<AlignFrame>();
+    Map<String, AlignFrame> dna = new HashMap<String, AlignFrame>();
+
+    /*
+     * Identify the DNA alignments
+     */
+    for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
+            .entrySet())
+    {
+      AlignFrame af = candidate.getValue();
+      if (af.getViewport().getAlignment().isNucleotide())
+      {
+        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))
+        {
+          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.
@@ -2183,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
@@ -2192,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
     {
@@ -2217,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;
@@ -2300,10 +2656,10 @@ 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;
 
@@ -2335,10 +2691,10 @@ public class Jalview2XML
       {
         if (hiddenSeqs == null)
         {
-          hiddenSeqs = new Vector();
+          hiddenSeqs = new ArrayList<SequenceI>();
         }
 
-        hiddenSeqs.addElement(seqRefIds.get(seqId));
+        hiddenSeqs.add(seqRefIds.get(seqId));
       }
 
     }
@@ -2346,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++)
@@ -2379,7 +2732,7 @@ public class Jalview2XML
     }
     // ///////////////////////////////
 
-    Hashtable pdbloaded = new Hashtable();
+    Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
     if (!multipleView)
     {
       // load sequence features, database references and any associated PDB
@@ -2425,7 +2778,17 @@ public class Jalview2XML
           {
             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()))
@@ -2438,8 +2801,7 @@ public class Jalview2XML
               }
             }
             StructureSelectionManager.getStructureSelectionManager(
-                    Desktop.instance)
-                    .registerPDBEntry(entry);
+                    Desktop.instance).registerPDBEntry(entry);
             al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
           }
         }
@@ -2456,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 = seqRefIds
-                    .get(maps[m].getDnasq());
+            SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
             // Load Mapping
             jalview.datamodel.Mapping mapping = null;
             // attach to dna sequence reference.
@@ -2499,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)
     {
@@ -2523,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);
@@ -2564,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++)
@@ -2605,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
@@ -2645,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)
           {
-            jaa.createSequenceMapping(al.findName(an[i].getSequenceRef()),
-                    1, true);
-            al.findName(an[i].getSequenceRef()).addAlignmentAnnotation(jaa);
+            // in pre-2.9 projects sequence ref is to sequence name
+            sequence = al.findName(sequenceRef);
+          }
+          if (sequence != null)
+          {
+            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)
           {
@@ -2731,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 = seqRefIds
-                  .get(seqId);
+          String seqId = jGroup.getSeq(s) + "";
+          SequenceI ts = seqRefIds.get(seqId);
 
           if (ts != null)
           {
@@ -2775,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,
@@ -2814,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)
@@ -2846,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);
         }
       }
     }
@@ -2938,90 +3285,202 @@ public class Jalview2XML
       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)
     {
-      loadStructures(jprovider, jseqs, af, ap);
+      ex.printStackTrace();
     }
-    // and finally return.
-    return af;
   }
 
   /**
@@ -3032,14 +3491,14 @@ public class Jalview2XML
    * @param af
    * @param ap
    */
-  protected void loadStructures(jarInputStreamProvider jprovider,
+  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, ViewerData> structureViewers = new LinkedHashMap<String, ViewerData>();
+    Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
 
     for (int i = 0; i < jseqs.length; i++)
     {
@@ -3052,10 +3511,10 @@ public class Jalview2XML
           for (int s = 0; s < structureStateCount; s++)
           {
             // check to see if we haven't already created this structure view
-            final StructureState structureState = ids[p].getStructureState(s);
+            final StructureState structureState = ids[p]
+                    .getStructureState(s);
             String sviewid = (structureState.getViewId() == null) ? null
-                    : structureState.getViewId()
-                            + uniqueSetSuffix;
+                    : 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
@@ -3072,8 +3531,8 @@ public class Jalview2XML
             // 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() + "");
+            jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
+                    .getId() + "");
             if (sviewid == null)
             {
               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
@@ -3081,8 +3540,10 @@ public class Jalview2XML
             }
             if (!structureViewers.containsKey(sviewid))
             {
-              structureViewers.put(sviewid, new ViewerData(x, y, width, height,
-                      false, false, true));
+              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
@@ -3092,7 +3553,7 @@ public class Jalview2XML
             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
             // seqs_file 2}, boolean[] {
             // linkAlignPanel,superposeWithAlignpanel}} from hash
-            ViewerData jmoldat = structureViewers.get(sviewid);
+            StructureViewerModel jmoldat = structureViewers.get(sviewid);
             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
                     | (structureState.hasAlignwithAlignPanel() ? structureState
                             .getAlignwithAlignPanel() : false));
@@ -3112,8 +3573,7 @@ public class Jalview2XML
              * pre-2.7 projects)
              */
             boolean colourByViewer = jmoldat.isColourByViewer();
-            colourByViewer &= structureState
-                    .hasColourByJmol() ? structureState
+            colourByViewer &= structureState.hasColourByJmol() ? structureState
                     .getColourByJmol() : true;
             jmoldat.setColourByViewer(colourByViewer);
 
@@ -3150,11 +3610,20 @@ public class Jalview2XML
         }
       }
     }
-      // Instantiate the associated structure views
-      for (Entry<String, ViewerData> entry : structureViewers.entrySet())
+    // Instantiate the associated structure views
+    for (Entry<String, StructureViewerModel> entry : structureViewers
+            .entrySet())
+    {
+      try
+      {
+        createOrLinkStructureViewer(entry, af, ap, jprovider);
+      } catch (Exception e)
       {
-        createOrLinkStructureViewer(entry, af, ap);
+        System.err.println("Error loading structure viewer: "
+                + e.getMessage());
+        // failed - try the next one
       }
+    }
   }
 
   /**
@@ -3162,12 +3631,13 @@ public class Jalview2XML
    * @param viewerData
    * @param af
    * @param ap
+   * @param jprovider
    */
   protected void createOrLinkStructureViewer(
-          Entry<String, ViewerData> viewerData, AlignFrame af,
-          AlignmentPanel ap)
+          Entry<String, StructureViewerModel> viewerData, AlignFrame af,
+          AlignmentPanel ap, jarInputStreamProvider jprovider)
   {
-    final ViewerData svattrib = viewerData.getValue();
+    final StructureViewerModel stateData = viewerData.getValue();
 
     /*
      * Search for any viewer windows already open from other alignment views
@@ -3177,68 +3647,80 @@ public class Jalview2XML
 
     if (comp != null)
     {
-      linkStructureViewer(ap, comp, svattrib);
+      linkStructureViewer(ap, comp, stateData);
       return;
     }
 
     /*
-     * Pending an XML element for ViewerType, just check if stateData contains
-     * "chimera" (part of the chimera session filename).
+     * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
+     * "viewer_"+stateData.viewId
      */
-    if (svattrib.getStateData().indexOf("chimera") > -1)
+    if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
     {
-      createChimeraViewer(viewerData, af);
+      createChimeraViewer(viewerData, af, jprovider);
     }
     else
     {
-      createJmolViewer(viewerData, af);
+      /*
+       * else Jmol (if pre-2.9, stateData contains JMOL state string)
+       */
+      createJmolViewer(viewerData, af, jprovider);
     }
   }
 
   /**
    * Create a new Chimera viewer.
    * 
-   * @param viewerData
+   * @param data
    * @param af
+   * @param jprovider
    */
-  protected void createChimeraViewer(Entry<String, ViewerData> viewerData,
-          AlignFrame af)
+  protected void createChimeraViewer(
+          Entry<String, StructureViewerModel> viewerData, AlignFrame af,
+          jarInputStreamProvider jprovider)
   {
-    final ViewerData data = viewerData.getValue();
-    String chimeraSession = data.getStateData();
-
-    if (new File(chimeraSession).exists())
-    {
-      Set<Entry<File, StructureData>> fileData = data.getFileData()
-              .entrySet();
-      List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
-      List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
-      for (Entry<File, StructureData> pdb : fileData)
-      {
-        String filePath = pdb.getValue().getFilePath();
-        String pdbId = pdb.getValue().getPdbId();
-        pdbs.add(new PDBEntry(filePath, pdbId));
-        final List<SequenceI> seqList = pdb.getValue().getSeqList();
-        SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
-        allseqs.add(seqs);
-      }
+    StructureViewerModel data = viewerData.getValue();
+    String chimeraSessionFile = data.getStateData();
 
-      boolean colourByChimera = data.isColourByViewer();
-      boolean colourBySequence = data.isColourWithAlignPanel();
-
-      // TODO can/should this be done via StructureViewer (like Jmol)?
-      final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs
-              .size()]);
-      final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs.size()][]);
-      new ChimeraViewFrame(chimeraSession, af.alignPanel, pdbArray,
-              seqsArray,
-              colourByChimera, colourBySequence);
-    }
-    else
-    {
-      Cache.log.error("Chimera session file " + chimeraSession
-              + " not found");
-    }
+    /*
+     * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
+     * 
+     * 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());
   }
 
   /**
@@ -3248,12 +3730,27 @@ public class Jalview2XML
    * 
    * @param viewerData
    * @param af
+   * @param jprovider
    */
   protected void createJmolViewer(
-          final Entry<String, ViewerData> viewerData, AlignFrame af)
+          final Entry<String, StructureViewerModel> viewerData,
+          AlignFrame af, jarInputStreamProvider jprovider)
   {
-    final ViewerData svattrib = viewerData.getValue();
+    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>();
@@ -3277,8 +3774,7 @@ public class Jalview2XML
         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
         pdbfilenames.add(filedat.getFilePath());
         pdbids.add(filedat.getPdbId());
-        seqmaps.add(filedat.getSeqList()
-                .toArray(new SequenceI[0]));
+        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.
@@ -3302,8 +3798,7 @@ public class Jalview2XML
         newFileLoc.append(filedat.getFilePath());
         pdbfilenames.add(filedat.getFilePath());
         pdbids.add(filedat.getPdbId());
-        seqmaps.add(filedat.getSeqList()
-                .toArray(new SequenceI[0]));
+        seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
         newFileLoc.append(" \"");
         newFileLoc.append(filedat.getFilePath());
         newFileLoc.append("\"");
@@ -3312,16 +3807,23 @@ public class Jalview2XML
       newFileLoc.append(";");
     }
 
-    if (newFileLoc.length() > 0)
+    if (newFileLoc.length() == 0)
+    {
+      return;
+    }
+    int histbug = newFileLoc.indexOf("history = ");
+    if (histbug > -1)
     {
-      int histbug = newFileLoc.indexOf("history = ");
+      /*
+       * 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"))
+        if (val.contains("e")) // eh? what can it be?
         {
           if (val.trim().equals("true"))
           {
@@ -3334,56 +3836,67 @@ public class Jalview2XML
           newFileLoc.replace(histbug, diff, val);
         }
       }
+    }
 
-      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
+    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()
       {
-        javax.swing.SwingUtilities.invokeAndWait(new Runnable()
+        @Override
+        public void run()
         {
-          @Override
-          public void run()
+          JalviewStructureDisplayI sview = null;
+          try
           {
-            JalviewStructureDisplayI sview = null;
-            try
-            {
-              // JAL-1333 note - we probably can't migrate Jmol views to UCSF
-              // Chimera!
-              sview = new StructureViewer(alf.alignPanel
-                      .getStructureSelectionManager()).createView(
-                      StructureViewer.ViewerType.JMOL, pdbf, id, sq,
-                      alf.alignPanel, svattrib, fileloc, rect, sviewid);
-              addNewStructureViewer(sview);
-            } catch (OutOfMemoryError ex)
+            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())
             {
-              new OOMWarning("restoring structure view for PDB id " + id,
-                      (OutOfMemoryError) ex.getCause());
-              if (sview != null && sview.isVisible())
-              {
-                sview.closeViewer();
-                sview.setVisible(false);
-                sview.dispose();
-              }
+              sview.closeViewer(false);
+              sview.setVisible(false);
+              sview.dispose();
             }
           }
-        });
-      } catch (InvocationTargetException ex)
-      {
-        warn("Unexpected error when opening Jmol view.", ex);
+        }
+      });
+    } catch (InvocationTargetException ex)
+    {
+      warn("Unexpected error when opening Jmol view.", ex);
 
-      } catch (InterruptedException e)
-      {
-        // e.printStackTrace();
-      }
+    } 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;
   }
 
   /**
@@ -3395,10 +3908,10 @@ public class Jalview2XML
    * @return
    */
   protected StructureViewerBase findMatchingViewer(
-          Entry<String, ViewerData> viewerData)
+          Entry<String, StructureViewerModel> viewerData)
   {
     final String sviewid = viewerData.getKey();
-    final ViewerData svattrib = viewerData.getValue();
+    final StructureViewerModel svattrib = viewerData.getValue();
     StructureViewerBase comp = null;
     JInternalFrame[] frames = getAllFrames();
     for (JInternalFrame frame : frames)
@@ -3409,11 +3922,11 @@ public class Jalview2XML
          * Post jalview 2.4 schema includes structure view id
          */
         if (sviewid != null
-                && ((StructureViewerBase) frame).getViewId().equals(
-                        sviewid))
+                && ((StructureViewerBase) frame).getViewId()
+                        .equals(sviewid))
         {
-          comp = (AppJmol) frame;
-          // todo: break?
+          comp = (StructureViewerBase) frame;
+          break; // break added in 2.9
         }
         /*
          * Otherwise test for matching position and size of viewer frame
@@ -3423,8 +3936,8 @@ public class Jalview2XML
                 && frame.getHeight() == svattrib.getHeight()
                 && frame.getWidth() == svattrib.getWidth())
         {
-          comp = (AppJmol) frame;
-          // todo: break?
+          comp = (StructureViewerBase) frame;
+          // no break in faint hope of an exact match on viewId
         }
       }
     }
@@ -3442,15 +3955,15 @@ public class Jalview2XML
    * @param viewerColouring
    */
   protected void linkStructureViewer(AlignmentPanel ap,
-          StructureViewerBase viewer, ViewerData svattrib)
+          StructureViewerBase viewer, StructureViewerModel stateData)
   {
     // NOTE: if the jalview project is part of a shared session then
     // view synchronization should/could be done here.
 
-    final boolean useinViewerSuperpos = svattrib.isAlignWithPanel();
-    final boolean usetoColourbyseq = svattrib.isColourWithAlignPanel();
-    final boolean viewerColouring = svattrib.isColourByViewer();
-    Map<File, StructureData> oldFiles = svattrib.getFileData();
+    final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
+    final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
+    final boolean viewerColouring = stateData.isColourByViewer();
+    Map<File, StructureData> oldFiles = stateData.getFileData();
 
     /*
      * Add mapping for sequences in this view to an already open viewer
@@ -3524,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")
@@ -3595,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(),
@@ -3612,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
       {
@@ -3649,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);
 
     }
@@ -3677,27 +4193,30 @@ public class Jalview2XML
     af.viewport.setConservationSelected(view.getConservationSelected());
     af.viewport.setShowJVSuffix(view.getShowFullId());
     af.viewport.setRightAlignIds(view.getRightAlignIds());
-    af.viewport.setFont(new java.awt.Font(view.getFontName(), view
-            .getFontStyle(), view.getFontSize()));
-    af.alignPanel.fontChanged();
+    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)
@@ -3738,10 +4257,8 @@ public class Jalview2XML
 
     af.viewport.setColourAppliesToAllGroups(true);
 
-    if (view.getShowSequenceFeatures())
-    {
-      af.viewport.showSequenceFeatures = true;
-    }
+    af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
+
     if (view.hasCentreColumnLabels())
     {
       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
@@ -3753,7 +4270,7 @@ public class Jalview2XML
     }
     if (view.hasFollowHighlight())
     {
-      af.viewport.followHighlight = view.getFollowHighlight();
+      af.viewport.setFollowHighlight(view.getFollowHighlight());
     }
     if (view.hasFollowSelection())
     {
@@ -3782,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())
     {
@@ -3808,9 +4325,14 @@ public class Jalview2XML
     // recover featre settings
     if (jms.getFeatureSettings() != null)
     {
-      af.viewport.setFeaturesDisplayed(new Hashtable());
+      FeaturesDisplayed fdi;
+      af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
       String[] renderOrder = new String[jms.getFeatureSettings()
               .getSettingCount()];
+      Hashtable featureGroups = new Hashtable();
+      Hashtable featureColours = new Hashtable();
+      Hashtable featureOrder = new Hashtable();
+
       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
       {
         Setting setting = jms.getFeatureSettings().getSetting(fs);
@@ -3837,41 +4359,42 @@ public class Jalview2XML
             gc.setColourByLabel(setting.getColourByLabel());
           }
           // and put in the feature colour table.
-          af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer().setColour(
-                  setting.getType(), gc);
+          featureColours.put(setting.getType(), gc);
         }
         else
         {
-          af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer().setColour(
-                  setting.getType(),
+          featureColours.put(setting.getType(),
                   new java.awt.Color(setting.getColour()));
         }
         renderOrder[fs] = setting.getType();
         if (setting.hasOrder())
         {
-          af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer().setOrder(
-                  setting.getType(), setting.getOrder());
+          featureOrder.put(setting.getType(), setting.getOrder());
         }
         else
         {
-          af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer().setOrder(
-                  setting.getType(),
-                  fs / jms.getFeatureSettings().getSettingCount());
+          featureOrder.put(setting.getType(), new Float(fs
+                  / jms.getFeatureSettings().getSettingCount()));
         }
         if (setting.getDisplay())
         {
-          af.viewport.getFeaturesDisplayed().put(setting.getType(), new Integer(
-                  setting.getColour()));
+          fdi.setVisible(setting.getType());
         }
       }
-      af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer().renderOrder = renderOrder;
-      Hashtable fgtable;
-      af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer().featureGroups = fgtable = new Hashtable();
+      Hashtable fgtable = new Hashtable();
       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
       {
         Group grp = jms.getFeatureSettings().getGroup(gs);
         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
       }
+      // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
+      // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
+      // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
+      FeatureRendererSettings frs = new FeatureRendererSettings(
+              renderOrder, fgtable, featureColours, 1.0f, featureOrder);
+      af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
+              .transferSettings(frs);
+
     }
 
     if (view.getHiddenColumnsCount() > 0)
@@ -3901,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;
   }
 
@@ -4036,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
@@ -4045,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)
@@ -4060,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
@@ -4236,9 +4774,8 @@ public class Jalview2XML
   {
     // 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();
@@ -4313,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())
@@ -4340,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;
   }
@@ -4362,7 +4903,7 @@ public class Jalview2XML
   {
     if (datasetIds == null)
     {
-      datasetIds = new Hashtable();
+      datasetIds = new Hashtable<String, Alignment>();
     }
     datasetIds.put(datasetId, dataset);
   }
@@ -4373,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)
     {
@@ -4385,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)
       {
@@ -4453,8 +4994,7 @@ public class Jalview2XML
         }
         else
         {
-          frefedSequence.add(new Object[]
-          { dsfor, jmap });
+          frefedSequence.add(new Object[] { dsfor, jmap });
         }
       }
       else
@@ -4463,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
         {
@@ -4508,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)
     {
@@ -4529,7 +5068,7 @@ public class Jalview2XML
       frefedSequence = new Vector();
     }
 
-    viewportsAdded = new Hashtable();
+    viewportsAdded.clear();
 
     AlignFrame af = loadFromObject(jm, null, false, null);
     af.alignPanels.clear();
@@ -4676,13 +5215,9 @@ public class Jalview2XML
       }
       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;
@@ -4734,4 +5269,76 @@ 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++;
+  }
 }