Merge commit 'alpha/update_2_12_for_2_11_2_series_merge^2' into HEAD
[jalview.git] / src / jalview / project / Jalview2XML.java
index 9b6741b..7e77cbf 100644 (file)
  */
 package jalview.project;
 
-import java.util.Locale;
-
 import static jalview.math.RotatableMatrix.Axis.X;
 import static jalview.math.RotatableMatrix.Axis.Y;
 import static jalview.math.RotatableMatrix.Axis.Z;
 
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.Rectangle;
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.lang.reflect.InvocationTargetException;
-import java.math.BigInteger;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.Vector;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-import java.util.jar.JarOutputStream;
-
-import javax.swing.JInternalFrame;
-import javax.swing.SwingUtilities;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBElement;
-import javax.xml.bind.Marshaller;
-import javax.xml.datatype.DatatypeConfigurationException;
-import javax.xml.datatype.DatatypeFactory;
-import javax.xml.datatype.XMLGregorianCalendar;
-import javax.xml.stream.XMLInputFactory;
-import javax.xml.stream.XMLStreamReader;
-
 import jalview.analysis.Conservation;
 import jalview.analysis.PCA;
 import jalview.analysis.scoremodels.ScoreModels;
 import jalview.analysis.scoremodels.SimilarityParams;
+import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureColourI;
 import jalview.api.ViewStyleI;
 import jalview.api.analysis.ScoreModelI;
@@ -92,6 +42,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.GeneLocus;
 import jalview.datamodel.GraphLine;
+import jalview.datamodel.HiddenMarkovModel;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.Point;
 import jalview.datamodel.RnaViewerModel;
@@ -109,7 +60,9 @@ import jalview.gui.AlignFrame;
 import jalview.gui.AlignViewport;
 import jalview.gui.AlignmentPanel;
 import jalview.gui.AppVarna;
+import jalview.gui.ChimeraViewFrame;
 import jalview.gui.Desktop;
+import jalview.gui.FeatureRenderer;
 import jalview.gui.JvOptionPane;
 import jalview.gui.OOMWarning;
 import jalview.gui.PCAPanel;
@@ -122,6 +75,7 @@ import jalview.gui.TreePanel;
 import jalview.io.BackupFiles;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
+import jalview.io.HMMFile;
 import jalview.io.NewickFile;
 import jalview.math.Matrix;
 import jalview.math.MatrixI;
@@ -147,9 +101,9 @@ import jalview.viewmodel.ViewportRanges;
 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
-import jalview.ws.jws2.Jws2Discoverer;
+import jalview.ws.api.ServiceWithParameters;
+import jalview.ws.jws2.PreferredServiceRegistry;
 import jalview.ws.jws2.dm.AAConSettings;
-import jalview.ws.jws2.jabaws2.Jws2Instance;
 import jalview.ws.params.ArgumentI;
 import jalview.ws.params.AutoCalcSetting;
 import jalview.ws.params.WsParamSetI;
@@ -201,6 +155,56 @@ import jalview.xml.binding.jalview.SequenceSet.SequenceSetProperties;
 import jalview.xml.binding.jalview.ThresholdType;
 import jalview.xml.binding.jalview.VAMSAS;
 
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Rectangle;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.math.BigInteger;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Vector;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+
+import javax.swing.JInternalFrame;
+import javax.swing.SwingUtilities;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.Marshaller;
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+import javax.xml.datatype.XMLGregorianCalendar;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamReader;
+
 /**
  * Write out the current jalview desktop state as a Jalview XML stream.
  * 
@@ -226,6 +230,7 @@ public class Jalview2XML
 
   private static final String RNA_PREFIX = "rna_";
 
+  private static final String HMMER_PREFIX = "hmmer_";
   private static final String UTF_8 = "UTF-8";
 
   /**
@@ -269,6 +274,25 @@ public class Jalview2XML
   private Map<RnaModel, String> rnaSessions = new HashMap<>();
 
   /**
+   * contains last error message (if any) encountered by XML loader.
+   */
+  String errorMessage = null;
+
+  /**
+   * flag to control whether the Jalview2XML_V1 parser should be deferred to if
+   * exceptions are raised during project XML parsing
+   */
+  public boolean attemptversion1parse = false;
+
+  /*
+   * JalviewJS only -- to allow read file bytes to be saved in the
+   * created AlignFrame, allowing File | Reload of a project file to work
+   * 
+   * BH 2019 JAL-3436
+   */
+  private File jarFile;
+
+  /**
    * A helper method for safely using the value of an optional attribute that
    * may be null if not present in the XML. Answers the boolean value, or false
    * if null.
@@ -428,7 +452,7 @@ public class Jalview2XML
    * @param _jmap
    * @return
    */
-  public SeqFref newMappingRef(final String sref,
+  protected SeqFref newMappingRef(final String sref,
           final jalview.datamodel.Mapping _jmap)
   {
     SeqFref fref = new SeqFref(sref, "Mapping")
@@ -450,7 +474,7 @@ public class Jalview2XML
     return fref;
   }
 
-  public SeqFref newAlcodMapRef(final String sref,
+  protected SeqFref newAlcodMapRef(final String sref,
           final AlignedCodonFrame _cf,
           final jalview.datamodel.Mapping _jmap)
   {
@@ -482,7 +506,7 @@ public class Jalview2XML
     return fref;
   }
 
-  public void resolveFrefedSequences()
+  protected void resolveFrefedSequences()
   {
     Iterator<SeqFref> nextFref = frefedSequence.iterator();
     int toresolve = frefedSequence.size();
@@ -627,8 +651,8 @@ public class Jalview2XML
    * core method for storing state for a set of AlignFrames.
    * 
    * @param frames
-   *          - frames involving all data to be exported (including containing
-   *          splitframes)
+   *          - frames involving all data to be exported (including those
+   *          contained in splitframes, though not the split frames themselves)
    * @param jout
    *          - project output stream
    */
@@ -656,21 +680,23 @@ public class Jalview2XML
       for (int i = frames.size() - 1; i > -1; i--)
       {
         AlignFrame af = frames.get(i);
+        AlignViewport vp = af.getViewport();
         // skip ?
         if (skipList != null && skipList
-                .containsKey(af.getViewport().getSequenceSetId()))
+                .containsKey(vp.getSequenceSetId()))
         {
           continue;
         }
 
         String shortName = makeFilename(af, shortNames);
 
-        int apSize = af.getAlignPanels().size();
+        AlignmentI alignment = vp.getAlignment();
+        List<? extends AlignmentViewPanel> panels = af.getAlignPanels();
+        int apSize = panels.size();
 
         for (int ap = 0; ap < apSize; ap++)
-        {
-          AlignmentPanel apanel = (AlignmentPanel) af.getAlignPanels()
-                  .get(ap);
+          {
+          AlignmentPanel apanel = (AlignmentPanel) panels.get(ap);
           String fileName = apSize == 1 ? shortName : ap + shortName;
           if (!fileName.endsWith(".xml"))
           {
@@ -679,10 +705,17 @@ public class Jalview2XML
 
           saveState(apanel, fileName, jout, viewIds);
 
-          String dssid = getDatasetIdRef(
-                  af.getViewport().getAlignment().getDataset());
+        }
+        if (apSize > 0)
+        {
+          // BH moved next bit out of inner loop, not that it really matters.
+          // so we are testing to make sure we actually have an alignment,
+          // apparently.
+          String dssid = getDatasetIdRef(alignment.getDataset());
           if (!dsses.containsKey(dssid))
           {
+            // We have not already covered this data by reference from another
+            // frame.
             dsses.put(dssid, af);
           }
         }
@@ -760,8 +793,8 @@ public class Jalview2XML
       // create backupfiles object and get new temp filename destination
       boolean doBackup = BackupFiles.getEnabled();
       BackupFiles backupfiles = doBackup ? new BackupFiles(jarFile) : null;
-      FileOutputStream fos = new FileOutputStream(
-              doBackup ? backupfiles.getTempFilePath() : jarFile);
+      FileOutputStream fos = new FileOutputStream(doBackup ? 
+              backupfiles.getTempFilePath() : jarFile);
 
       JarOutputStream jout = new JarOutputStream(fos);
       List<AlignFrame> frames = new ArrayList<>();
@@ -800,10 +833,21 @@ public class Jalview2XML
     }
   }
 
+  /**
+   * Each AlignFrame has a single data set associated with it. Note that none of
+   * these frames are split frames, because Desktop.getAlignFrames() collects
+   * top and bottom separately here.
+   * 
+   * @param dsses
+   * @param fileName
+   * @param jout
+   */
   private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
           String fileName, JarOutputStream jout)
   {
 
+    // Note that in saveAllFrames we have associated each specific dataset to
+    // ONE of its associated frames.
     for (String dssids : dsses.keySet())
     {
       AlignFrame _af = dsses.get(dssids);
@@ -830,7 +874,7 @@ public class Jalview2XML
    * @param out
    *          jar entry name
    */
-  public JalviewModel saveState(AlignmentPanel ap, String fileName,
+  protected JalviewModel saveState(AlignmentPanel ap, String fileName,
           JarOutputStream jout, List<String> viewIds)
   {
     return saveState(ap, fileName, false, jout, viewIds);
@@ -852,7 +896,7 @@ public class Jalview2XML
    * @param out
    *          jar entry name
    */
-  public JalviewModel saveState(AlignmentPanel ap, String fileName,
+  protected JalviewModel saveState(AlignmentPanel ap, String fileName,
           boolean storeDS, JarOutputStream jout, List<String> viewIds)
   {
     if (viewIds == null)
@@ -960,7 +1004,7 @@ public class Jalview2XML
         else
         {
           vamsasSeq = createVamsasSequence(id, jds);
-          // vamsasSet.addSequence(vamsasSeq);
+//          vamsasSet.addSequence(vamsasSeq);
           vamsasSet.getSequence().add(vamsasSeq);
           vamsasSetIds.put(id, vamsasSeq);
           seqRefIds.put(id, jds);
@@ -1066,6 +1110,9 @@ public class Jalview2XML
         jseq.getFeatures().add(features);
       }
 
+      /*
+       * save PDB entries for sequence
+       */
       if (jdatasq.getAllPDBEntries() != null)
       {
         Enumeration<PDBEntry> en = jdatasq.getAllPDBEntries().elements();
@@ -1084,7 +1131,7 @@ public class Jalview2XML
            * only view *should* be coped with sensibly.
            */
           // This must have been loaded, is it still visible?
-          JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+          JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
           String matchedFile = null;
           for (int f = frames.length - 1; f > -1; f--)
           {
@@ -1160,6 +1207,10 @@ public class Jalview2XML
 
       saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
 
+      if (jds.hasHMMProfile())
+      {
+        saveHmmerProfile(jout, jseq, jds);
+      }
       // jms.addJSeq(jseq);
       object.getJSeq().add(jseq);
     }
@@ -1238,9 +1289,9 @@ public class Jalview2XML
     {
       // FIND ANY ASSOCIATED TREES
       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
-      if (Desktop.desktop != null)
+      if (Desktop.getDesktopPane() != null)
       {
-        JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+        JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
 
         for (int t = 0; t < frames.length; t++)
         {
@@ -1284,9 +1335,9 @@ public class Jalview2XML
     /*
      * save PCA viewers
      */
-    if (!storeDS && Desktop.desktop != null)
+    if (!storeDS && Desktop.getDesktopPane() != null)
     {
-      for (JInternalFrame frame : Desktop.desktop.getAllFrames())
+      for (JInternalFrame frame : Desktop.getDesktopPane().getAllFrames())
       {
         if (frame instanceof PCAPanel)
         {
@@ -1355,8 +1406,9 @@ public class Jalview2XML
 
             if (colourScheme instanceof jalview.schemes.UserColourScheme)
             {
-              jGroup.setColour(setUserColourScheme(colourScheme,
-                      userColours, object));
+              jGroup.setColour(
+                      setUserColourScheme(colourScheme, userColours,
+                              object));
             }
             else
             {
@@ -1402,7 +1454,7 @@ public class Jalview2XML
         }
       }
 
-      // jms.setJGroup(groups);
+      //jms.setJGroup(groups);
       Object group;
       for (JGroup grp : groups)
       {
@@ -1539,13 +1591,11 @@ public class Jalview2XML
              * save any filter for the feature type
              */
             FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
-            if (filter != null)
-            {
-              Iterator<FeatureMatcherI> filters = filter.getMatchers()
-                      .iterator();
+            if (filter != null)  {
+              Iterator<FeatureMatcherI> filters = filter.getMatchers().iterator();
               FeatureMatcherI firstFilter = filters.next();
-              setting.setMatcherSet(Jalview2XML.marshalFilter(firstFilter,
-                      filters, filter.isAnded()));
+              setting.setMatcherSet(Jalview2XML.marshalFilter(
+                      firstFilter, filters, filter.isAnded()));
             }
 
             /*
@@ -1594,7 +1644,8 @@ public class Jalview2XML
 
             setting.setDisplay(
                     av.getFeaturesDisplayed().isVisible(featureType));
-            float rorder = fr.getOrder(featureType);
+            float rorder = fr
+                    .getOrder(featureType);
             if (rorder > -1)
             {
               setting.setOrder(rorder);
@@ -1618,7 +1669,7 @@ public class Jalview2XML
           Group g = new Group();
           g.setName(grp);
           g.setDisplay(((Boolean) fr.checkGroupVisibility(grp, false))
-                  .booleanValue());
+                          .booleanValue());
           // fs.addGroup(g);
           fs.getGroup().add(g);
           groupsAdded.addElement(grp);
@@ -1708,7 +1759,39 @@ public class Jalview2XML
     }
     return object;
   }
+  /**
+   * Saves the HMMER profile associated with the sequence as a file in the jar,
+   * in HMMER format, and saves the name of the file as a child element of the
+   * XML sequence element
+   * 
+   * @param jout
+   * @param xmlSeq
+   * @param seq
+   */
+  protected void saveHmmerProfile(JarOutputStream jout, JSeq xmlSeq,
+          SequenceI seq)
+  {
+    HiddenMarkovModel profile = seq.getHMM();
+    if (profile == null)
+    {
+      warn("Want to save HMM profile for " + seq.getName()
+              + " but none found");
+      return;
+    }
+    HMMFile hmmFile = new HMMFile(profile);
+    String hmmAsString = hmmFile.print();
+    String jarEntryName = HMMER_PREFIX + nextCounter();
+    try
+    {
+      writeJarEntry(jout, jarEntryName, hmmAsString.getBytes());
+      xmlSeq.setHmmerProfile(jarEntryName);
+    } catch (IOException e)
+    {
+      warn("Error saving HMM profile: " + e.getMessage());
+    }
+  }
 
+    
   /**
    * Writes PCA viewer attributes and computed values to an XML model object and
    * adds it to the JalviewModel. Any exceptions are reported by logging.
@@ -1927,11 +2010,11 @@ public class Jalview2XML
           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
           boolean storeDataset)
   {
-    if (Desktop.desktop == null)
+    if (Desktop.getDesktopPane() == null)
     {
       return;
     }
-    JInternalFrame[] frames = Desktop.desktop.getAllFrames();
+    JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
     for (int f = frames.length - 1; f > -1; f--)
     {
       if (frames[f] instanceof AppVarna)
@@ -2375,9 +2458,8 @@ public class Jalview2XML
   {
     if (calcIdParam.getVersion().equals("1.0"))
     {
-      final String[] calcIds = calcIdParam.getServiceURL()
-              .toArray(new String[0]);
-      Jws2Instance service = Jws2Discoverer.getDiscoverer()
+      final String[] calcIds = calcIdParam.getServiceURL().toArray(new String[0]);
+      ServiceWithParameters service = PreferredServiceRegistry.getRegistry()
               .getPreferredServiceFor(calcIds);
       if (service != null)
       {
@@ -2410,7 +2492,7 @@ public class Jalview2XML
           argList = parmSet.getArguments();
           parmSet = null;
         }
-        AAConSettings settings = new AAConSettings(
+        AutoCalcSetting settings = new AAConSettings(
                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
                 calcIdParam.isNeedsUpdate());
@@ -2418,7 +2500,7 @@ public class Jalview2XML
       }
       else
       {
-        warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
+        warn("Cannot resolve a service for the parameters used in this project. Try configuring a server in the Web Services preferences tab.");
         return false;
       }
     }
@@ -2538,8 +2620,8 @@ public class Jalview2XML
         }
         if (ref.hasMap())
         {
-          Mapping mp = createVamsasMapping(ref.getMap(), parentseq, jds,
-                  recurse);
+          Mapping mp = createVamsasMapping(ref.getMap(), parentseq,
+                  jds, recurse);
           dbref.setMapping(mp);
         }
         vamsasSeq.getDBRef().add(dbref);
@@ -2714,7 +2796,6 @@ public class Jalview2XML
 
     return ucs;
   }
-
   /**
    * contains last error message (if any) encountered by XML loader.
    */
@@ -2760,7 +2841,10 @@ public class Jalview2XML
     {
       try
       {
-        SwingUtilities.invokeAndWait(new Runnable()
+// was invokeAndWait
+         
+         // BH 2019 -- can't wait
+        SwingUtilities.invokeLater(new Runnable()
         {
           @Override
           public void run()
@@ -2773,15 +2857,14 @@ public class Jalview2XML
         System.err.println("Error loading alignment: " + x.getMessage());
       }
     }
+    this.jarFile = null;
     return af;
   }
 
-  @SuppressWarnings("unused")
+       @SuppressWarnings("unused")
   private jarInputStreamProvider createjarInputStreamProvider(
           final Object ofile) throws MalformedURLException
   {
-
-    // BH 2018 allow for bytes already attached to File object
     try
     {
       String file = (ofile instanceof File
@@ -2789,6 +2872,10 @@ public class Jalview2XML
               : ofile.toString());
       byte[] bytes = Platform.isJS() ? Platform.getFileBytes((File) ofile)
               : null;
+      if (bytes != null)
+      {
+        this.jarFile = (File) ofile;
+      }
       URL url = null;
       errorMessage = null;
       uniqueSetSuffix = null;
@@ -2800,31 +2887,21 @@ public class Jalview2XML
       {
         url = new URL(file);
       }
-      final URL _url = url;
       return new jarInputStreamProvider()
       {
-
         @Override
         public JarInputStream getJarInputStream() throws IOException
         {
-          if (bytes != null)
-          {
-            // System.out.println("Jalview2XML: opening byte jarInputStream for
-            // bytes.length=" + bytes.length);
-            return new JarInputStream(new ByteArrayInputStream(bytes));
-          }
-          if (_url != null)
-          {
-            // System.out.println("Jalview2XML: opening url jarInputStream for "
-            // + _url);
-            return new JarInputStream(_url.openStream());
-          }
-          else
-          {
-            // System.out.println("Jalview2XML: opening file jarInputStream for
-            // " + file);
-            return new JarInputStream(new FileInputStream(file));
-          }
+          InputStream is = bytes != null ? new ByteArrayInputStream(bytes)
+                  : (url != null ? url.openStream()
+                          : new FileInputStream(file));
+          return new JarInputStream(is);
+        }
+
+        @Override
+        public File getFile()
+        {
+          return jarFile;
         }
 
         @Override
@@ -2863,13 +2940,18 @@ public class Jalview2XML
     AlignFrame af = null, _af = null;
     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
     Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
-    final String file = jprovider.getFilename();
+    String fileName = jprovider.getFilename();
+    File file = jprovider.getFile();
+    List<AlignFrame> alignFrames = new ArrayList<>();
     try
     {
       JarInputStream jin = null;
       JarEntry jarentry = null;
       int entryCount = 1;
 
+      // Look for all the entry names ending with ".xml"
+      // This includes all panels and at least one frame.
+//      Platform.timeCheck(null, Platform.TIME_MARK);
       do
       {
         jin = jprovider.getJarInputStream();
@@ -2877,24 +2959,52 @@ public class Jalview2XML
         {
           jarentry = jin.getNextJarEntry();
         }
-
-        if (jarentry != null && jarentry.getName().endsWith(".xml"))
-        {
+        String name = (jarentry == null ? null : jarentry.getName());
+
+//        System.out.println("Jalview2XML opening " + name);
+        if (name != null && name.endsWith(".xml"))
+        {
+          // DataSet for.... is read last.
+          
+          
+          // The question here is what to do with the two
+          // .xml files in the jvp file.
+          // Some number of them, "...Dataset for...", will be the
+          // Only AlignPanels and will have Viewport.
+          // One or more will be the source data, with the DBRefs.
+          //
+          // JVP file writing (above) ensures tha the AlignPanels are written
+          // first, then all relevant datasets (which are
+          // Jalview.datamodel.Alignment).
+          //
+
+//          Platform.timeCheck("Jalview2XML JAXB " + name, Platform.TIME_MARK);
           JAXBContext jc = JAXBContext
                   .newInstance("jalview.xml.binding.jalview");
           XMLStreamReader streamReader = XMLInputFactory.newInstance()
                   .createXMLStreamReader(jin);
           javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
-          JAXBElement<JalviewModel> jbe = um.unmarshal(streamReader,
-                  JalviewModel.class);
-          JalviewModel object = jbe.getValue();
+          JAXBElement<JalviewModel> jbe = um
+                  .unmarshal(streamReader, JalviewModel.class);
+          JalviewModel model = jbe.getValue();
 
           if (true) // !skipViewport(object))
           {
-            _af = loadFromObject(object, file, true, jprovider);
-            if (_af != null && object.getViewport().size() > 0)
-            // getJalviewModelSequence().getViewportCount() > 0)
+            // Q: Do we have to load from the model, even if it
+            // does not have a viewport, could we discover that early on?
+            // Q: Do we need to load this object?
+            _af = loadFromObject(model, fileName, file, true, jprovider);
+//            Platform.timeCheck("Jalview2XML.loadFromObject",
+            // Platform.TIME_MARK);
+
+            if (_af != null)
             {
+              alignFrames.add(_af);
+            }
+            if (_af != null && model.getViewport().size() > 0)
+            {
+
+              // That is, this is one of the AlignmentPanel models
               if (af == null)
               {
                 // store a reference to the first view
@@ -2926,7 +3036,7 @@ public class Jalview2XML
     } catch (IOException ex)
     {
       ex.printStackTrace();
-      errorMessage = "Couldn't locate Jalview XML file : " + file;
+      errorMessage = "Couldn't locate Jalview XML file : " + fileName;
       System.err.println(
               "Exception whilst loading jalview XML file : " + ex + "\n");
     } catch (Exception ex)
@@ -2937,9 +3047,9 @@ public class Jalview2XML
       {
         // used to attempt to parse as V1 castor-generated xml
       }
-      if (Desktop.instance != null)
+      if (Desktop.getInstance() != null)
       {
-        Desktop.instance.stopLoading();
+        Desktop.getInstance().stopLoading();
       }
       if (af != null)
       {
@@ -2956,6 +3066,12 @@ public class Jalview2XML
       errorMessage = "Out of memory loading jalview XML file";
       System.err.println("Out of memory whilst loading jalview XML file");
       e.printStackTrace();
+    } finally
+    {
+      for (AlignFrame alf : alignFrames)
+      {
+        alf.alignPanel.setHoldRepaint(false);
+      }
     }
 
     /*
@@ -2967,7 +3083,7 @@ public class Jalview2XML
      */
     for (AlignFrame fr : gatherToThisFrame.values())
     {
-      Desktop.instance.gatherViews(fr);
+      Desktop.getInstance().gatherViews(fr);
     }
 
     restoreSplitFrames();
@@ -2975,8 +3091,7 @@ public class Jalview2XML
     {
       if (ds.getCodonFrames() != null)
       {
-        StructureSelectionManager
-                .getStructureSelectionManager(Desktop.instance)
+        Desktop.getStructureSelectionManager()
                 .registerMappings(ds.getCodonFrames());
       }
     }
@@ -2985,9 +3100,9 @@ public class Jalview2XML
       reportErrors();
     }
 
-    if (Desktop.instance != null)
+    if (Desktop.getInstance() != null)
     {
-      Desktop.instance.stopLoading();
+      Desktop.getInstance().stopLoading();
     }
 
     return af;
@@ -3068,7 +3183,7 @@ public class Jalview2XML
      */
     for (SplitFrame sf : gatherTo)
     {
-      Desktop.instance.gatherViews(sf);
+      Desktop.getInstance().gatherViews(sf);
     }
 
     splitFrameCandidates.clear();
@@ -3127,7 +3242,7 @@ public class Jalview2XML
           @Override
           public void run()
           {
-            JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+            JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
                     finalErrorMessage,
                     "Error " + (saving ? "saving" : "loading")
                             + " Jalview file",
@@ -3188,33 +3303,34 @@ public class Jalview2XML
    * @param prefix
    *          a prefix for the temporary file name, must be at least three
    *          characters long
-   * @param suffixModel
+   * @param origFile
    *          null or original file - so new file can be given the same suffix
    *          as the old one
    * @return
    */
   protected String copyJarEntry(jarInputStreamProvider jprovider,
-          String jarEntryName, String prefix, String suffixModel)
+          String jarEntryName, String prefix, String origFile)
   {
+    BufferedReader in = null;
+    PrintWriter out = null;
     String suffix = ".tmp";
-    if (suffixModel == null)
+    if (origFile == null)
     {
-      suffixModel = jarEntryName;
+      origFile = jarEntryName;
     }
-    int sfpos = suffixModel.lastIndexOf(".");
-    if (sfpos > -1 && sfpos < (suffixModel.length() - 1))
+    int sfpos = origFile.lastIndexOf(".");
+    if (sfpos > -1 && sfpos < (origFile.length() - 3))
     {
-      suffix = "." + suffixModel.substring(sfpos + 1);
+      suffix = "." + origFile.substring(sfpos + 1);
     }
-
     try (JarInputStream jin = jprovider.getJarInputStream())
     {
+
       JarEntry entry = null;
       do
       {
         entry = jin.getNextJarEntry();
       } while (entry != null && !entry.getName().equals(jarEntryName));
-
       if (entry != null)
       {
         // in = new BufferedReader(new InputStreamReader(jin, UTF_8));
@@ -3259,23 +3375,25 @@ public class Jalview2XML
   }
 
   /**
-   * Load alignment frame from jalview XML DOM object
+   * Load alignment frame from jalview XML DOM object. For a DOM object that
+   * includes one or more Viewport elements (one with a title that does NOT
+   * contain "Dataset for"), create the frame.
    * 
    * @param jalviewModel
    *          DOM
-   * @param file
+   * @param fileName
    *          filename source string
+   * @param file 
    * @param loadTreesAndStructures
    *          when false only create Viewport
    * @param jprovider
    *          data source provider
    * @return alignment frame created from view stored in DOM
    */
-  AlignFrame loadFromObject(JalviewModel jalviewModel, String file,
-          boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
+  AlignFrame loadFromObject(JalviewModel jalviewModel, String fileName,
+          File file, boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
   {
-    SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet()
-            .get(0);
+    SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet().get(0);
     List<Sequence> vamsasSeqs = vamsasSet.getSequence();
 
     // JalviewModelSequence jms = object.getJalviewModelSequence();
@@ -3334,10 +3452,11 @@ public class Jalview2XML
           if (tmpSeq.getStart() != jseq.getStart()
                   || tmpSeq.getEnd() != jseq.getEnd())
           {
-            System.err.println(String.format(
-                    "Warning JAL-2154 regression: updating start/end for sequence %s from %d/%d to %d/%d",
-                    tmpSeq.getName(), tmpSeq.getStart(), tmpSeq.getEnd(),
-                    jseq.getStart(), jseq.getEnd()));
+            System.err.println(
+                    String.format("Warning JAL-2154 regression: updating start/end for sequence %s from %d/%d to %d/%d",
+                            tmpSeq.getName(), tmpSeq.getStart(),
+                            tmpSeq.getEnd(), jseq.getStart(),
+                            jseq.getEnd()));
           }
         }
         else
@@ -3572,8 +3691,7 @@ public class Jalview2XML
             {
               entry.setProperty(prop.getName(), prop.getValue());
             }
-            StructureSelectionManager
-                    .getStructureSelectionManager(Desktop.instance)
+            Desktop.getStructureSelectionManager()
                     .registerPDBEntry(entry);
             // adds PDBEntry to datasequence's set (since Jalview 2.10)
             if (al.getSequenceAt(i).getDatasetSequence() != null)
@@ -3586,6 +3704,16 @@ public class Jalview2XML
             }
           }
         }
+        /*
+         * load any HMMER profile
+         */
+        // TODO fix this
+
+        String hmmJarFile = jseqs.get(i).getHmmerProfile();
+        if (hmmJarFile != null && jprovider != null)
+        {
+          loadHmmerProfile(jprovider, hmmJarFile, al.getSequenceAt(i));
+        }
       }
     } // end !multipleview
 
@@ -3620,8 +3748,8 @@ public class Jalview2XML
               else
               {
                 // defer to later
-                frefedSequence
-                        .add(newAlcodMapRef(map.getDnasq(), cf, mapping));
+                frefedSequence.add(
+                        newAlcodMapRef(map.getDnasq(), cf, mapping));
               }
             }
           }
@@ -3723,6 +3851,7 @@ public class Jalview2XML
             }
           }
         }
+        // create the new AlignmentAnnotation
         jalview.datamodel.AlignmentAnnotation jaa = null;
 
         if (annotation.isGraph())
@@ -3759,6 +3888,7 @@ public class Jalview2XML
           jaa._linecolour = firstColour;
         }
         // register new annotation
+        // Annotation graphs such as Conservation will not have id.
         if (annotation.getId() != null)
         {
           annotationIds.put(annotation.getId(), jaa);
@@ -3829,7 +3959,8 @@ public class Jalview2XML
         jaa.setCalcId(annotation.getCalcId());
         if (annotation.getProperty().size() > 0)
         {
-          for (Annotation.Property prop : annotation.getProperty())
+          for (Annotation.Property prop : annotation
+                  .getProperty())
           {
             jaa.setProperty(prop.getName(), prop.getValue());
           }
@@ -3970,8 +4101,6 @@ public class Jalview2XML
     // ///////////////////////////////
     // LOAD VIEWPORT
 
-    AlignFrame af = null;
-    AlignViewport av = null;
     // now check to see if we really need to create a new viewport.
     if (multipleView && viewportsAdded.size() == 0)
     {
@@ -4010,8 +4139,9 @@ public class Jalview2XML
     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
             jalviewModel.getVersion());
 
+    AlignFrame af = null;
     AlignmentPanel ap = null;
-    boolean isnewview = true;
+    AlignViewport av = null;
     if (viewId != null)
     {
       // Check to see if this alignment already has a view id == viewId
@@ -4021,25 +4151,27 @@ public class Jalview2XML
       {
         for (int v = 0; v < views.length; v++)
         {
-          if (views[v].av.getViewId().equalsIgnoreCase(viewId))
+          ap = views[v];
+          av = ap.av;
+          if (av.getViewId().equalsIgnoreCase(viewId))
           {
             // recover the existing alignpanel, alignframe, viewport
-            af = views[v].alignFrame;
-            av = views[v].av;
-            ap = views[v];
+            af = ap.alignFrame;
+            break;
             // TODO: could even skip resetting view settings if we don't want to
             // change the local settings from other jalview processes
-            isnewview = false;
           }
         }
       }
     }
 
-    if (isnewview)
+    if (af == null)
     {
-      af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
+      af = loadViewport(fileName, file, jseqs, hiddenSeqs, al, jalviewModel, view,
               uniqueSeqSetId, viewId, autoAlan);
       av = af.getViewport();
+      // note that this only retrieves the most recently accessed
+      // tab of an AlignFrame.
       ap = af.alignPanel;
     }
 
@@ -4048,18 +4180,90 @@ public class Jalview2XML
      * 
      * Not done if flag is false (when this method is used for New View)
      */
+    final AlignFrame af0 = af;
+    final AlignViewport av0 = av;
+    final AlignmentPanel ap0 = ap;
+//    Platform.timeCheck("Jalview2XML.loadFromObject-beforetree",
+//            Platform.TIME_MARK);
     if (loadTreesAndStructures)
     {
-      loadTrees(jalviewModel, view, af, av, ap);
-      loadPCAViewers(jalviewModel, ap);
-      loadPDBStructures(jprovider, jseqs, af, ap);
-      loadRnaViewers(jprovider, jseqs, ap);
+      if (!jalviewModel.getTree().isEmpty())
+      {
+        SwingUtilities.invokeLater(new Runnable()
+        {
+          @Override
+          public void run()
+          {
+//            Platform.timeCheck(null, Platform.TIME_MARK);
+            loadTrees(jalviewModel, view, af0, av0, ap0);
+//            Platform.timeCheck("Jalview2XML.loadTrees", Platform.TIME_MARK);
+          }
+        });
+      }
+      if (!jalviewModel.getPcaViewer().isEmpty())
+      {
+        SwingUtilities.invokeLater(new Runnable()
+        {
+          @Override
+          public void run()
+          {
+//            Platform.timeCheck(null, Platform.TIME_MARK);
+            loadPCAViewers(jalviewModel, ap0);
+//            Platform.timeCheck("Jalview2XML.loadPCA", Platform.TIME_MARK);
+          }
+        });
+      }
+      SwingUtilities.invokeLater(new Runnable()
+      {
+        @Override
+        public void run()
+        {
+//          Platform.timeCheck(null, Platform.TIME_MARK);
+          loadPDBStructures(jprovider, jseqs, af0, ap0);
+//          Platform.timeCheck("Jalview2XML.loadPDB", Platform.TIME_MARK);
+        }
+      });
+      SwingUtilities.invokeLater(new Runnable()
+      {
+        @Override
+        public void run()
+        {
+          loadRnaViewers(jprovider, jseqs, ap0);
+        }
+      });
     }
     // and finally return.
+    // but do not set holdRepaint true just yet, because this could be the
+    // initial frame with just its dataset.
     return af;
   }
 
   /**
+   * Loads a HMMER profile from a file stored in the project, and associates it
+   * with the specified sequence
+   * 
+   * @param jprovider
+   * @param hmmJarFile
+   * @param seq
+   */
+  protected void loadHmmerProfile(jarInputStreamProvider jprovider,
+          String hmmJarFile, SequenceI seq)
+  {
+    try
+    {
+      String hmmFile = copyJarEntry(jprovider, hmmJarFile, "hmm", null);
+      HMMFile parser = new HMMFile(hmmFile, DataSourceType.FILE);
+      HiddenMarkovModel hmmModel = parser.getHMM();
+      hmmModel = new HiddenMarkovModel(hmmModel, seq);
+      seq.setHMM(hmmModel);
+    } catch (IOException e)
+    {
+      warn("Error loading HMM profile for " + seq.getName() + ": "
+              + e.getMessage());
+    }
+  }
+
+  /**
    * 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.
@@ -4072,7 +4276,7 @@ public class Jalview2XML
    * @param jseqs
    * @param ap
    */
-  private void loadRnaViewers(jarInputStreamProvider jprovider,
+  protected void loadRnaViewers(jarInputStreamProvider jprovider,
           List<JSeq> jseqs, AlignmentPanel ap)
   {
     /*
@@ -4164,8 +4368,8 @@ public class Jalview2XML
    * @param av
    * @param ap
    */
-  protected void loadTrees(JalviewModel jm, Viewport view, AlignFrame af,
-          AlignViewport av, AlignmentPanel ap)
+  protected void loadTrees(JalviewModel jm, Viewport view,
+          AlignFrame af, AlignViewport av, AlignmentPanel ap)
   {
     // TODO result of automated refactoring - are all these parameters needed?
     try
@@ -4182,6 +4386,12 @@ public class Jalview2XML
                   tree.getTitle(), safeInt(tree.getWidth()),
                   safeInt(tree.getHeight()), safeInt(tree.getXpos()),
                   safeInt(tree.getYpos()));
+          if (tp == null)
+          {
+            warn("There was a problem recovering stored Newick tree: \n"
+                    + tree.getNewick());
+            continue;
+          }
           if (tree.getId() != null)
           {
             // perhaps bind the tree id to something ?
@@ -4202,12 +4412,6 @@ public class Jalview2XML
           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
         }
         tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
-        if (tp == null)
-        {
-          warn("There was a problem recovering stored Newick tree: \n"
-                  + tree.getNewick());
-          continue;
-        }
 
         tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
         tp.fitToWindow_actionPerformed(null);
@@ -4273,8 +4477,8 @@ 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 = pdbid.getStructureState()
-                    .get(s);
+            final StructureState structureState = pdbid
+                    .getStructureState().get(s);
             String sviewid = (structureState.getViewId() == null) ? null
                     : structureState.getViewId() + uniqueSetSuffix;
             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
@@ -4291,7 +4495,7 @@ public class Jalview2XML
             int height = safeInt(structureState.getHeight());
 
             // Probably don't need to do this anymore...
-            // Desktop.desktop.getComponentAt(x, y);
+            // Desktop.getDesktop().getComponentAt(x, y);
             // TODO: NOW: check that this recovers the PDB file correctly.
             String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
                     pdbid.getFile());
@@ -4342,8 +4546,8 @@ public class Jalview2XML
             colourByViewer &= structureState.isColourByJmol();
             jmoldat.setColourByViewer(colourByViewer);
 
-            if (jmoldat.getStateData().length() < structureState.getValue()
-                    /*Content()*/.length())
+            if (jmoldat.getStateData().length() < structureState
+                    .getValue()/*Content()*/.length())
             {
               jmoldat.setStateData(structureState.getValue());// Content());
             }
@@ -4423,38 +4627,208 @@ public class Jalview2XML
       // TODO JAL-3619 show error dialog / offer an alternative viewer
       Cache.log.error("Invalid structure viewer type: " + type);
     }
-  }
 
-  /**
-   * Generates a name for the entry in the project jar file to hold state
-   * information for a structure viewer
-   * 
-   * @param viewId
-   * @return
-   */
-  protected String getViewerJarEntryName(String viewId)
-  {
-    return VIEWER_PREFIX + viewId;
   }
 
   /**
-   * Returns any open frame that matches given structure viewer data. The match
-   * is based on the unique viewId, or (for older project versions) the frame's
-   * geometry.
+   * Creates a new structure viewer window
    * 
+   * @param viewerType
    * @param viewerData
-   * @return
+   * @param af
+   * @param jprovider
    */
-  protected StructureViewerBase findMatchingViewer(
-          Entry<String, StructureViewerModel> viewerData)
+  protected void createStructureViewer(ViewerType viewerType,
+          final Entry<String, StructureViewerModel> viewerData,
+          AlignFrame af, jarInputStreamProvider jprovider)
   {
-    final String sviewid = viewerData.getKey();
-    final StructureViewerModel svattrib = viewerData.getValue();
-    StructureViewerBase comp = null;
-    JInternalFrame[] frames = getAllFrames();
-    for (JInternalFrame frame : frames)
-    {
-      if (frame instanceof StructureViewerBase)
+    final StructureViewerModel viewerModel = viewerData.getValue();
+    String sessionFilePath = null;
+
+    if (viewerType == ViewerType.JMOL)
+    {
+      sessionFilePath = rewriteJmolSession(viewerModel, jprovider);
+    }
+    else
+    {
+      String viewerJarEntryName = getViewerJarEntryName(
+              viewerModel.getViewId());
+      sessionFilePath = copyJarEntry(jprovider, viewerJarEntryName,
+              "viewerSession", ".tmp");
+    }
+    final String sessionPath = sessionFilePath;
+    final String sviewid = viewerData.getKey();
+// BH again was invokeAndWait
+    // try
+    // {
+      javax.swing.SwingUtilities.invokeLater(new Runnable()
+      {
+        @Override
+        public void run()
+        {
+          JalviewStructureDisplayI sview = null;
+          try
+          {
+            sview = StructureViewer.createView(viewerType, af.alignPanel,
+                    viewerModel, sessionPath, sviewid);
+            addNewStructureViewer(sview);
+          } catch (OutOfMemoryError ex)
+          {
+            new OOMWarning("Restoring structure view for " + viewerType,
+                    (OutOfMemoryError) ex.getCause());
+            if (sview != null && sview.isVisible())
+            {
+              sview.closeViewer(false);
+              sview.setVisible(false);
+              sview.dispose();
+            }
+          }
+        }
+      });
+//    } catch (InvocationTargetException | InterruptedException ex)
+//    {
+//      warn("Unexpected error when opening " + viewerType
+//              + " structure viewer", ex);
+//    }
+  }
+
+  /**
+   * Rewrites a Jmol session script, saves it to a temporary file, and returns
+   * the path of the file. "load file" commands are rewritten to change the
+   * original PDB file names to those created as the Jalview project is loaded.
+   * 
+   * @param svattrib
+   * @param jprovider
+   * @return
+   */
+  private String rewriteJmolSession(StructureViewerModel svattrib,
+          jarInputStreamProvider jprovider)
+  {
+    String state = svattrib.getStateData(); // Jalview < 2.9
+    if (state == null || state.isEmpty()) // Jalview >= 2.9
+    {
+      String jarEntryName = getViewerJarEntryName(svattrib.getViewId());
+      state = readJarEntry(jprovider, jarEntryName);
+    }
+    // TODO or simpler? for each key in oldFiles,
+    // replace key.getPath() in state with oldFiles.get(key).getFilePath()
+    // (allowing for different path escapings)
+    StringBuilder rewritten = new StringBuilder(state.length());
+    int cp = 0, ncp, ecp;
+    Map<File, StructureData> oldFiles = svattrib.getFileData();
+    while ((ncp = state.indexOf("load ", cp)) > -1)
+    {
+      do
+      {
+        // look for next filename in load statement
+        rewritten.append(state.substring(cp,
+                ncp = (state.indexOf("\"", ncp + 1) + 1)));
+        String oldfilenam = state.substring(ncp,
+                ecp = state.indexOf("\"", ncp));
+        // recover the new mapping data for this old filename
+        // have to normalize filename - since Jmol and jalview do
+        // filename translation differently.
+        StructureData filedat = oldFiles.get(new File(oldfilenam));
+        if (filedat == null)
+        {
+          String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
+          filedat = oldFiles.get(new File(reformatedOldFilename));
+        }
+        rewritten.append(Platform.escapeBackslashes(filedat.getFilePath()));
+        rewritten.append("\"");
+        cp = ecp + 1; // advance beyond last \" and set cursor so we can
+                      // look for next file statement.
+      } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
+    }
+    if (cp > 0)
+    {
+      // just append rest of state
+      rewritten.append(state.substring(cp));
+    }
+    else
+    {
+      System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
+      rewritten = new StringBuilder(state);
+      rewritten.append("; load append ");
+      for (File id : oldFiles.keySet())
+      {
+        // add pdb files that should be present in the viewer
+        StructureData filedat = oldFiles.get(id);
+        rewritten.append(" \"").append(filedat.getFilePath()).append("\"");
+      }
+      rewritten.append(";");
+    }
+
+    if (rewritten.length() == 0)
+    {
+      return null;
+    }
+    final String history = "history = ";
+    int historyIndex = rewritten.indexOf(history);
+    if (historyIndex > -1)
+    {
+      /*
+       * change "history = [true|false];" to "history = [1|0];"
+       */
+      historyIndex += history.length();
+      String val = rewritten.substring(historyIndex, historyIndex + 5);
+      if (val.startsWith("true"))
+      {
+        rewritten.replace(historyIndex, historyIndex + 4, "1");
+      }
+      else if (val.startsWith("false"))
+      {
+        rewritten.replace(historyIndex, historyIndex + 5, "0");
+      }
+    }
+
+    try
+    {
+      File tmp = File.createTempFile("viewerSession", ".tmp");
+      try (OutputStream os = new FileOutputStream(tmp))
+      {
+        InputStream is = new ByteArrayInputStream(
+                rewritten.toString().getBytes());
+        copyAll(is, os);
+        return tmp.getAbsolutePath();
+      }
+    } catch (IOException e)
+    {
+      Cache.log.error("Error restoring Jmol session: " + e.toString());
+    }
+    return null;
+  }
+
+  /**
+   * Generates a name for the entry in the project jar file to hold state
+   * information for a structure viewer
+   * 
+   * @param viewId
+   * @return
+   */
+  protected String getViewerJarEntryName(String viewId)
+  {
+    return VIEWER_PREFIX + viewId;
+  }
+
+  /**
+   * Returns any open frame that matches given structure viewer data. The match
+   * is based on the unique viewId, or (for older project versions) the frame's
+   * geometry.
+   * 
+   * @param viewerData
+   * @return
+   */
+  protected StructureViewerBase findMatchingViewer(
+          Entry<String, StructureViewerModel> viewerData)
+  {
+    final String sviewid = viewerData.getKey();
+    final StructureViewerModel svattrib = viewerData.getValue();
+    StructureViewerBase comp = null;
+    JInternalFrame[] frames = getAllFrames();
+    for (JInternalFrame frame : frames)
+    {
+      if (frame instanceof StructureViewerBase)
       {
         /*
          * Post jalview 2.4 schema includes structure view id
@@ -4550,7 +4924,7 @@ public class Jalview2XML
     {
       try
       {
-        frames = Desktop.desktop.getAllFrames();
+        frames = Desktop.getDesktopPane().getAllFrames();
       } catch (ArrayIndexOutOfBoundsException e)
       {
         // occasional No such child exceptions are thrown here...
@@ -4620,7 +4994,7 @@ public class Jalview2XML
     }
   }
 
-  AlignFrame loadViewport(String file, List<JSeq> JSEQ,
+  AlignFrame loadViewport(String fileName, File file, List<JSeq> JSEQ,
           List<SequenceI> hiddenSeqs, AlignmentI al, JalviewModel jm,
           Viewport view, String uniqueSeqSetId, String viewId,
           List<JvAnnotRow> autoAlan)
@@ -4640,7 +5014,9 @@ public class Jalview2XML
     // }
     ;
 
-    af.setFileName(file, FileFormat.Jalview);
+    af.alignPanel.setHoldRepaint(true);
+    af.setFile(fileName, file, null, FileFormat.Jalview);
+    af.setFileObject(jarFile); // BH 2019 JAL-3436
 
     final AlignViewport viewport = af.getViewport();
     for (int i = 0; i < JSEQ.size(); i++)
@@ -4747,8 +5123,18 @@ public class Jalview2XML
       viewport.setViewName(view.getViewName());
       af.setInitialTabVisible();
     }
-    af.setBounds(safeInt(view.getXpos()), safeInt(view.getYpos()),
-            safeInt(view.getWidth()), safeInt(view.getHeight()));
+    int x = safeInt(view.getXpos());
+    int y = safeInt(view.getYpos());
+    int w = safeInt(view.getWidth());
+    int h = safeInt(view.getHeight());
+    // // BH we cannot let the title bar go off the top
+    // if (Platform.isJS())
+    // {
+    // x = Math.max(50 - w, x);
+    // y = Math.max(0, y);
+    // }
+
+    af.setBounds(x, y, w, h);
     // startSeq set in af.alignPanel.updateLayout below
     af.alignPanel.updateLayout();
     ColourSchemeI cs = null;
@@ -4970,8 +5356,9 @@ public class Jalview2XML
     String complementaryViewId = view.getComplementId();
     if (complementaryViewId == null)
     {
-      Desktop.addInternalFrame(af, view.getTitle(),
+      Dimension dim = Platform.getDimIfEmbedded(af,
               safeInt(view.getWidth()), safeInt(view.getHeight()));
+      Desktop.addInternalFrame(af, view.getTitle(), dim.width, dim.height);
       // recompute any autoannotation
       af.alignPanel.updateAnnotation(false, true);
       reorderAutoannotation(af, al, autoAlan);
@@ -5229,16 +5616,16 @@ public class Jalview2XML
     String id = object.getViewport().get(0).getSequenceSetId();
     if (skipList.containsKey(id))
     {
-      if (Cache.log != null && Cache.log.isDebugEnabled())
-      {
-        Cache.log.debug("Skipping seuqence set id " + id);
-      }
+       if (Cache.log != null && Cache.log.isDebugEnabled())
+        {
+          Cache.log.debug("Skipping seuqence set id " + id);
+        }
       return true;
     }
     return false;
   }
 
-  public void addToSkipList(AlignFrame af)
+  protected void addToSkipList(AlignFrame af)
   {
     if (skipList == null)
     {
@@ -5247,7 +5634,7 @@ public class Jalview2XML
     skipList.put(af.getViewport().getSequenceSetId(), af);
   }
 
-  public void clearSkipList()
+  protected void clearSkipList()
   {
     if (skipList != null)
     {
@@ -5275,7 +5662,7 @@ public class Jalview2XML
         addDatasetRef(vamsasSet.getDatasetId(), ds);
       }
     }
-    Vector<SequenceI> dseqs = null;
+    Vector dseqs = null;
     if (!ignoreUnrefed)
     {
       // recovering an alignment View
@@ -5316,8 +5703,8 @@ public class Jalview2XML
       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
       dseqs.copyInto(dsseqs);
       ds = new jalview.datamodel.Alignment(dsseqs);
-      debug("Created new dataset " + vamsasSet.getDatasetId()
-              + " for alignment " + System.identityHashCode(al));
+//      debug("Jalview2XML Created new dataset " + vamsasSet.getDatasetId()
+//              + " for alignment " + System.identityHashCode(al));
       addDatasetRef(vamsasSet.getDatasetId(), ds);
     }
     // set the dataset for the newly imported alignment.
@@ -5375,7 +5762,6 @@ public class Jalview2XML
       }
     }
   }
-
   /**
    * 
    * @param vamsasSeq
@@ -5747,9 +6133,11 @@ public class Jalview2XML
 
     viewportsAdded.clear();
 
-    AlignFrame af = loadFromObject(jm, null, false, null);
+    AlignFrame af = loadFromObject(jm, null, null, false, null);
     af.getAlignPanels().clear();
     af.closeMenuItem_actionPerformed(true);
+    af.alignPanel.setHoldRepaint(false);
+    this.jarFile = null;
 
     /*
      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
@@ -5893,7 +6281,7 @@ public class Jalview2XML
       }
       else
       {
-        Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
+          Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
       }
     }
   }
@@ -6108,8 +6496,10 @@ public class Jalview2XML
                   axis.getXPos(), axis.getYPos(), axis.getZPos());
         }
 
+        Dimension dim = Platform.getDimIfEmbedded(panel, 475, 450);
         Desktop.addInternalFrame(panel, MessageManager.formatMessage(
-                "label.calc_title", "PCA", modelName), 475, 450);
+                "label.calc_title", "PCA", modelName), dim.width,
+                dim.height);
       }
     } catch (Exception ex)
     {
@@ -6118,182 +6508,14 @@ public class Jalview2XML
   }
 
   /**
-   * Creates a new structure viewer window
-   * 
-   * @param viewerType
-   * @param viewerData
-   * @param af
-   * @param jprovider
-   */
-  protected void createStructureViewer(ViewerType viewerType,
-          final Entry<String, StructureViewerModel> viewerData,
-          AlignFrame af, jarInputStreamProvider jprovider)
-  {
-    final StructureViewerModel viewerModel = viewerData.getValue();
-    String sessionFilePath = null;
-
-    if (viewerType == ViewerType.JMOL)
-    {
-      sessionFilePath = rewriteJmolSession(viewerModel, jprovider);
-    }
-    else
-    {
-      String viewerJarEntryName = getViewerJarEntryName(
-              viewerModel.getViewId());
-      sessionFilePath = copyJarEntry(jprovider, viewerJarEntryName,
-              "viewerSession", ".tmp");
-    }
-    final String sessionPath = sessionFilePath;
-    final String sviewid = viewerData.getKey();
-    try
-    {
-      SwingUtilities.invokeAndWait(new Runnable()
-      {
-        @Override
-        public void run()
-        {
-          JalviewStructureDisplayI sview = null;
-          try
-          {
-            sview = StructureViewer.createView(viewerType, af.alignPanel,
-                    viewerModel, sessionPath, sviewid);
-            addNewStructureViewer(sview);
-          } catch (OutOfMemoryError ex)
-          {
-            new OOMWarning("Restoring structure view for " + viewerType,
-                    (OutOfMemoryError) ex.getCause());
-            if (sview != null && sview.isVisible())
-            {
-              sview.closeViewer(false);
-              sview.setVisible(false);
-              sview.dispose();
-            }
-          }
-        }
-      });
-    } catch (InvocationTargetException | InterruptedException ex)
-    {
-      warn("Unexpected error when opening " + viewerType
-              + " structure viewer", ex);
-    }
-  }
-
-  /**
-   * Rewrites a Jmol session script, saves it to a temporary file, and returns
-   * the path of the file. "load file" commands are rewritten to change the
-   * original PDB file names to those created as the Jalview project is loaded.
-   * 
-   * @param svattrib
-   * @param jprovider
-   * @return
-   */
-  private String rewriteJmolSession(StructureViewerModel svattrib,
-          jarInputStreamProvider jprovider)
-  {
-    String state = svattrib.getStateData(); // Jalview < 2.9
-    if (state == null || state.isEmpty()) // Jalview >= 2.9
-    {
-      String jarEntryName = getViewerJarEntryName(svattrib.getViewId());
-      state = readJarEntry(jprovider, jarEntryName);
-    }
-    // TODO or simpler? for each key in oldFiles,
-    // replace key.getPath() in state with oldFiles.get(key).getFilePath()
-    // (allowing for different path escapings)
-    StringBuilder rewritten = new StringBuilder(state.length());
-    int cp = 0, ncp, ecp;
-    Map<File, StructureData> oldFiles = svattrib.getFileData();
-    while ((ncp = state.indexOf("load ", cp)) > -1)
-    {
-      do
-      {
-        // look for next filename in load statement
-        rewritten.append(state.substring(cp,
-                ncp = (state.indexOf("\"", ncp + 1) + 1)));
-        String oldfilenam = state.substring(ncp,
-                ecp = state.indexOf("\"", ncp));
-        // recover the new mapping data for this old filename
-        // have to normalize filename - since Jmol and jalview do
-        // filename translation differently.
-        StructureData filedat = oldFiles.get(new File(oldfilenam));
-        if (filedat == null)
-        {
-          String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
-          filedat = oldFiles.get(new File(reformatedOldFilename));
-        }
-        rewritten.append(Platform.escapeBackslashes(filedat.getFilePath()));
-        rewritten.append("\"");
-        cp = ecp + 1; // advance beyond last \" and set cursor so we can
-                      // look for next file statement.
-      } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
-    }
-    if (cp > 0)
-    {
-      // just append rest of state
-      rewritten.append(state.substring(cp));
-    }
-    else
-    {
-      System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
-      rewritten = new StringBuilder(state);
-      rewritten.append("; load append ");
-      for (File id : oldFiles.keySet())
-      {
-        // add pdb files that should be present in the viewer
-        StructureData filedat = oldFiles.get(id);
-        rewritten.append(" \"").append(filedat.getFilePath()).append("\"");
-      }
-      rewritten.append(";");
-    }
-
-    if (rewritten.length() == 0)
-    {
-      return null;
-    }
-    final String history = "history = ";
-    int historyIndex = rewritten.indexOf(history);
-    if (historyIndex > -1)
-    {
-      /*
-       * change "history = [true|false];" to "history = [1|0];"
-       */
-      historyIndex += history.length();
-      String val = rewritten.substring(historyIndex, historyIndex + 5);
-      if (val.startsWith("true"))
-      {
-        rewritten.replace(historyIndex, historyIndex + 4, "1");
-      }
-      else if (val.startsWith("false"))
-      {
-        rewritten.replace(historyIndex, historyIndex + 5, "0");
-      }
-    }
-
-    try
-    {
-      File tmp = File.createTempFile("viewerSession", ".tmp");
-      try (OutputStream os = new FileOutputStream(tmp))
-      {
-        InputStream is = new ByteArrayInputStream(
-                rewritten.toString().getBytes());
-        copyAll(is, os);
-        return tmp.getAbsolutePath();
-      }
-    } catch (IOException e)
-    {
-      Cache.log.error("Error restoring Jmol session: " + e.toString());
-    }
-    return null;
-  }
-
-  /**
    * Populates an XML model of the feature colour scheme for one feature type
    * 
    * @param featureType
    * @param fcol
    * @return
    */
-  public static Colour marshalColour(String featureType,
-          FeatureColourI fcol)
+  public static Colour marshalColour(
+          String featureType, FeatureColourI fcol)
   {
     Colour col = new Colour();
     if (fcol.isSimpleColour())
@@ -6354,7 +6576,6 @@ public class Jalview2XML
           boolean and)
   {
     jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
-
     if (filters.hasNext())
     {
       /*
@@ -6404,7 +6625,6 @@ public class Jalview2XML
       }
       result.setMatchCondition(matcherModel);
     }
-
     return result;
   }
 
@@ -6415,7 +6635,8 @@ public class Jalview2XML
    * @param matcherSetModel
    * @return
    */
-  public static FeatureMatcherSetI parseFilter(String featureType,
+  public static FeatureMatcherSetI parseFilter(
+          String featureType,
           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
   {
     FeatureMatcherSetI result = new FeatureMatcherSet();
@@ -6430,7 +6651,6 @@ public class Jalview2XML
                       featureType, e.getMessage()));
       // return as much as was parsed up to the error
     }
-
     return result;
   }
 
@@ -6445,7 +6665,8 @@ public class Jalview2XML
    * @throws IllegalStateException
    *           if AND and OR conditions are mixed
    */
-  protected static void parseFilterConditions(FeatureMatcherSetI matcherSet,
+  protected static void parseFilterConditions(
+          FeatureMatcherSetI matcherSet,
           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
           boolean and)
   {
@@ -6467,7 +6688,6 @@ public class Jalview2XML
       else if (filterBy == FilterBy.BY_SCORE)
       {
         matchCondition = FeatureMatcher.byScore(cond, pattern);
-
       }
       else if (filterBy == FilterBy.BY_ATTRIBUTE)
       {
@@ -6477,7 +6697,6 @@ public class Jalview2XML
         matchCondition = FeatureMatcher.byAttribute(cond, pattern,
                 attNames);
       }
-
       /*
        * note this throws IllegalStateException if AND-ing to a 
        * previously OR-ed compound condition, or vice versa
@@ -6520,22 +6739,19 @@ public class Jalview2XML
   public static FeatureColourI parseColour(Colour colourModel)
   {
     FeatureColourI colour = null;
-
     if (colourModel.getMax() != null)
     {
       Color mincol = null;
       Color maxcol = null;
       Color noValueColour = null;
-
       try
       {
         mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
         maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
       } catch (Exception e)
       {
-        Cache.log.warn("Couldn't parse out graduated feature color.", e);
+          Cache.log.warn("Couldn't parse out graduated feature color.", e);
       }
-
       NoValueColour noCol = colourModel.getNoValueColour();
       if (noCol == NoValueColour.MIN)
       {
@@ -6545,7 +6761,6 @@ public class Jalview2XML
       {
         noValueColour = maxcol;
       }
-
       colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
               safeFloat(colourModel.getMin()),
               safeFloat(colourModel.getMax()));
@@ -6584,7 +6799,6 @@ public class Jalview2XML
       Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
       colour = new FeatureColour(color);
     }
-
     return colour;
   }
 }