Merge branch 'develop' into merge/JAL-3127
[jalview.git] / src / jalview / project / Jalview2XML.java
index 5106e45..cf5974c 100644 (file)
  */
 package jalview.project;
 
+import static jalview.math.RotatableMatrix.Axis.X;
+import static jalview.math.RotatableMatrix.Axis.Y;
+import static jalview.math.RotatableMatrix.Axis.Z;
+
 import jalview.analysis.Conservation;
+import jalview.analysis.PCA;
+import jalview.analysis.scoremodels.ScoreModels;
+import jalview.analysis.scoremodels.SimilarityParams;
 import jalview.api.FeatureColourI;
 import jalview.api.ViewStyleI;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.SimilarityParamsI;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignedCodonFrame;
@@ -31,6 +40,7 @@ import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.GraphLine;
 import jalview.datamodel.PDBEntry;
+import jalview.datamodel.Point;
 import jalview.datamodel.RnaViewerModel;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
@@ -49,17 +59,21 @@ import jalview.gui.AppVarna;
 import jalview.gui.ChimeraViewFrame;
 import jalview.gui.Desktop;
 import jalview.gui.FeatureRenderer;
-import jalview.gui.Jalview2XML_V1;
 import jalview.gui.JvOptionPane;
 import jalview.gui.OOMWarning;
+import jalview.gui.PCAPanel;
 import jalview.gui.PaintRefresher;
 import jalview.gui.SplitFrame;
 import jalview.gui.StructureViewer;
 import jalview.gui.StructureViewer.ViewerType;
 import jalview.gui.StructureViewerBase;
 import jalview.gui.TreePanel;
+import jalview.io.BackupFiles;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
+import jalview.io.NewickFile;
+import jalview.math.Matrix;
+import jalview.math.MatrixI;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.ColourSchemeI;
@@ -76,6 +90,7 @@ import jalview.util.StringUtils;
 import jalview.util.jarInputStreamProvider;
 import jalview.util.matcher.Condition;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.PCAModel;
 import jalview.viewmodel.ViewportRanges;
 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
@@ -91,6 +106,8 @@ import jalview.xml.binding.jalview.Annotation;
 import jalview.xml.binding.jalview.Annotation.ThresholdLine;
 import jalview.xml.binding.jalview.AnnotationColourScheme;
 import jalview.xml.binding.jalview.AnnotationElement;
+import jalview.xml.binding.jalview.DoubleMatrix;
+import jalview.xml.binding.jalview.DoubleVector;
 import jalview.xml.binding.jalview.Feature;
 import jalview.xml.binding.jalview.Feature.OtherData;
 import jalview.xml.binding.jalview.FeatureMatcherSet.CompoundMatcher;
@@ -105,6 +122,11 @@ import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids;
 import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids.StructureState;
 import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer;
 import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer.SecondaryStructure;
+import jalview.xml.binding.jalview.JalviewModel.PcaViewer;
+import jalview.xml.binding.jalview.JalviewModel.PcaViewer.Axis;
+import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMax;
+import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMin;
+import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SequencePoint;
 import jalview.xml.binding.jalview.JalviewModel.Tree;
 import jalview.xml.binding.jalview.JalviewModel.UserColours;
 import jalview.xml.binding.jalview.JalviewModel.Viewport;
@@ -117,6 +139,7 @@ import jalview.xml.binding.jalview.MapListType.MapListTo;
 import jalview.xml.binding.jalview.Mapping;
 import jalview.xml.binding.jalview.NoValueColour;
 import jalview.xml.binding.jalview.ObjectFactory;
+import jalview.xml.binding.jalview.PcaDataType;
 import jalview.xml.binding.jalview.Pdbentry.Property;
 import jalview.xml.binding.jalview.Sequence;
 import jalview.xml.binding.jalview.Sequence.DBRef;
@@ -191,6 +214,12 @@ public class Jalview2XML
 
   private static final String UTF_8 = "UTF-8";
 
+  /**
+   * prefix for recovering datasets for alignments with multiple views where
+   * non-existent dataset IDs were written for some views
+   */
+  private static final String UNIQSEQSETID = "uniqueSeqSetId.";
+
   // use this with nextCounter() to make unique names for entities
   private int counter = 0;
 
@@ -226,6 +255,45 @@ public class Jalview2XML
   private Map<RnaModel, String> rnaSessions = new HashMap<>();
 
   /**
+   * 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.
+   * 
+   * @param b
+   * @return
+   */
+  public static boolean safeBoolean(Boolean b)
+  {
+    return b == null ? false : b.booleanValue();
+  }
+
+  /**
+   * A helper method for safely using the value of an optional attribute that
+   * may be null if not present in the XML. Answers the integer value, or zero
+   * if null.
+   * 
+   * @param i
+   * @return
+   */
+  public static int safeInt(Integer i)
+  {
+    return i == null ? 0 : i.intValue();
+  }
+
+  /**
+   * A helper method for safely using the value of an optional attribute that
+   * may be null if not present in the XML. Answers the float value, or zero if
+   * null.
+   * 
+   * @param f
+   * @return
+   */
+  public static float safeFloat(Float f)
+  {
+    return f == null ? 0f : f.floatValue();
+  }
+
+  /**
    * create/return unique hash string for sq
    * 
    * @param sq
@@ -483,24 +551,30 @@ public class Jalview2XML
   public void saveState(File statefile)
   {
     FileOutputStream fos = null;
+
     try
     {
+
       fos = new FileOutputStream(statefile);
+
       JarOutputStream jout = new JarOutputStream(fos);
       saveState(jout);
+      fos.close();
 
     } catch (Exception e)
     {
+      Cache.log.error("Couln't write Jalview state to " + statefile, e);
       // TODO: inform user of the problem - they need to know if their data was
       // not saved !
       if (errorMessage == null)
       {
-        errorMessage = "Couldn't write Jalview Archive to output file '"
+        errorMessage = "Did't write Jalview Archive to output file '"
                 + statefile + "' - See console error log for details";
       }
       else
       {
-        errorMessage += "(output file was '" + statefile + "')";
+        errorMessage += "(Didn't write Jalview Archive to output file '"
+                + statefile + ")";
       }
       e.printStackTrace();
     } finally
@@ -670,7 +744,11 @@ public class Jalview2XML
   {
     try
     {
-      FileOutputStream fos = new FileOutputStream(jarFile);
+      // create backupfiles object and get new temp filename destination
+      BackupFiles backupfiles = new BackupFiles(jarFile);
+      FileOutputStream fos = new FileOutputStream(
+              backupfiles.getTempFilePath());
+
       JarOutputStream jout = new JarOutputStream(fos);
       List<AlignFrame> frames = new ArrayList<>();
 
@@ -692,7 +770,12 @@ public class Jalview2XML
       }
       ;
       jout.close();
-      return true;
+      boolean success = true;
+
+      backupfiles.setWriteSuccess(success);
+      success = backupfiles.rollBackupsAndRenameTempFile();
+
+      return success;
     } catch (Exception ex)
     {
       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
@@ -1169,6 +1252,9 @@ public class Jalview2XML
               tree.setXpos(tp.getX());
               tree.setYpos(tp.getY());
               tree.setId(makeHashCode(tp, null));
+              tree.setLinkToAllViews(
+                      tp.getTreeCanvas().isApplyToAllViews());
+
               // jms.addTree(tree);
               object.getTree().add(tree);
             }
@@ -1177,6 +1263,24 @@ public class Jalview2XML
       }
     }
 
+    /*
+     * save PCA viewers
+     */
+    if (!storeDS && Desktop.desktop != null)
+    {
+      for (JInternalFrame frame : Desktop.desktop.getAllFrames())
+      {
+        if (frame instanceof PCAPanel)
+        {
+          PCAPanel panel = (PCAPanel) frame;
+          if (panel.getAlignViewport().getAlignment() == jal)
+          {
+            savePCA(panel, object);
+          }
+        }
+      }
+    }
+
     // SAVE ANNOTATIONS
     /**
      * store forward refs from an annotationRow to any groups
@@ -1584,6 +1688,196 @@ public class Jalview2XML
   }
 
   /**
+   * Writes PCA viewer attributes and computed values to an XML model object and
+   * adds it to the JalviewModel. Any exceptions are reported by logging.
+   */
+  protected void savePCA(PCAPanel panel, JalviewModel object)
+  {
+    try
+    {
+      PcaViewer viewer = new PcaViewer();
+      viewer.setHeight(panel.getHeight());
+      viewer.setWidth(panel.getWidth());
+      viewer.setXpos(panel.getX());
+      viewer.setYpos(panel.getY());
+      viewer.setTitle(panel.getTitle());
+      PCAModel pcaModel = panel.getPcaModel();
+      viewer.setScoreModelName(pcaModel.getScoreModelName());
+      viewer.setXDim(panel.getSelectedDimensionIndex(X));
+      viewer.setYDim(panel.getSelectedDimensionIndex(Y));
+      viewer.setZDim(panel.getSelectedDimensionIndex(Z));
+      viewer.setBgColour(
+              panel.getRotatableCanvas().getBackgroundColour().getRGB());
+      viewer.setScaleFactor(panel.getRotatableCanvas().getScaleFactor());
+      float[] spMin = panel.getRotatableCanvas().getSeqMin();
+      SeqPointMin spmin = new SeqPointMin();
+      spmin.setXPos(spMin[0]);
+      spmin.setYPos(spMin[1]);
+      spmin.setZPos(spMin[2]);
+      viewer.setSeqPointMin(spmin);
+      float[] spMax = panel.getRotatableCanvas().getSeqMax();
+      SeqPointMax spmax = new SeqPointMax();
+      spmax.setXPos(spMax[0]);
+      spmax.setYPos(spMax[1]);
+      spmax.setZPos(spMax[2]);
+      viewer.setSeqPointMax(spmax);
+      viewer.setShowLabels(panel.getRotatableCanvas().isShowLabels());
+      viewer.setLinkToAllViews(
+              panel.getRotatableCanvas().isApplyToAllViews());
+      SimilarityParamsI sp = pcaModel.getSimilarityParameters();
+      viewer.setIncludeGaps(sp.includeGaps());
+      viewer.setMatchGaps(sp.matchGaps());
+      viewer.setIncludeGappedColumns(sp.includeGappedColumns());
+      viewer.setDenominateByShortestLength(sp.denominateByShortestLength());
+
+      /*
+       * sequence points on display
+       */
+      for (jalview.datamodel.SequencePoint spt : pcaModel
+              .getSequencePoints())
+      {
+        SequencePoint point = new SequencePoint();
+        point.setSequenceRef(seqHash(spt.getSequence()));
+        point.setXPos(spt.coord.x);
+        point.setYPos(spt.coord.y);
+        point.setZPos(spt.coord.z);
+        viewer.getSequencePoint().add(point);
+      }
+
+      /*
+       * (end points of) axes on display
+       */
+      for (Point p : panel.getRotatableCanvas().getAxisEndPoints())
+      {
+
+        Axis axis = new Axis();
+        axis.setXPos(p.x);
+        axis.setYPos(p.y);
+        axis.setZPos(p.z);
+        viewer.getAxis().add(axis);
+      }
+
+      /*
+       * raw PCA data (note we are not restoring PCA inputs here -
+       * alignment view, score model, similarity parameters)
+       */
+      PcaDataType data = new PcaDataType();
+      viewer.setPcaData(data);
+      PCA pca = pcaModel.getPcaData();
+
+      DoubleMatrix pm = new DoubleMatrix();
+      saveDoubleMatrix(pca.getPairwiseScores(), pm);
+      data.setPairwiseMatrix(pm);
+
+      DoubleMatrix tm = new DoubleMatrix();
+      saveDoubleMatrix(pca.getTridiagonal(), tm);
+      data.setTridiagonalMatrix(tm);
+
+      DoubleMatrix eigenMatrix = new DoubleMatrix();
+      data.setEigenMatrix(eigenMatrix);
+      saveDoubleMatrix(pca.getEigenmatrix(), eigenMatrix);
+
+      object.getPcaViewer().add(viewer);
+    } catch (Throwable t)
+    {
+      Cache.log.error("Error saving PCA: " + t.getMessage());
+    }
+  }
+
+  /**
+   * Stores values from a matrix into an XML element, including (if present) the
+   * D or E vectors
+   * 
+   * @param m
+   * @param xmlMatrix
+   * @see #loadDoubleMatrix(DoubleMatrix)
+   */
+  protected void saveDoubleMatrix(MatrixI m, DoubleMatrix xmlMatrix)
+  {
+    xmlMatrix.setRows(m.height());
+    xmlMatrix.setColumns(m.width());
+    for (int i = 0; i < m.height(); i++)
+    {
+      DoubleVector row = new DoubleVector();
+      for (int j = 0; j < m.width(); j++)
+      {
+        row.getV().add(m.getValue(i, j));
+      }
+      xmlMatrix.getRow().add(row);
+    }
+    if (m.getD() != null)
+    {
+      DoubleVector dVector = new DoubleVector();
+      for (double d : m.getD())
+      {
+        dVector.getV().add(d);
+      }
+      xmlMatrix.setD(dVector);
+    }
+    if (m.getE() != null)
+    {
+      DoubleVector eVector = new DoubleVector();
+      for (double e : m.getE())
+      {
+        eVector.getV().add(e);
+      }
+      xmlMatrix.setE(eVector);
+    }
+  }
+
+  /**
+   * Loads XML matrix data into a new Matrix object, including the D and/or E
+   * vectors (if present)
+   * 
+   * @param mData
+   * @return
+   * @see Jalview2XML#saveDoubleMatrix(MatrixI, DoubleMatrix)
+   */
+  protected MatrixI loadDoubleMatrix(DoubleMatrix mData)
+  {
+    int rows = mData.getRows();
+    double[][] vals = new double[rows][];
+
+    for (int i = 0; i < rows; i++)
+    {
+      List<Double> dVector = mData.getRow().get(i).getV();
+      vals[i] = new double[dVector.size()];
+      int dvi = 0;
+      for (Double d : dVector)
+      {
+        vals[i][dvi++] = d;
+      }
+    }
+
+    MatrixI m = new Matrix(vals);
+
+    if (mData.getD() != null)
+    {
+      List<Double> dVector = mData.getD().getV();
+      double[] vec = new double[dVector.size()];
+      int dvi = 0;
+      for (Double d : dVector)
+      {
+        vec[dvi++] = d;
+      }
+      m.setD(vec);
+    }
+    if (mData.getE() != null)
+    {
+      List<Double> dVector = mData.getE().getV();
+      double[] vec = new double[dVector.size()];
+      int dvi = 0;
+      for (Double d : dVector)
+      {
+        vec[dvi++] = d;
+      }
+      m.setE(vec);
+    }
+
+    return m;
+  }
+
+  /**
    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
    * for each viewer, with
    * <ul>
@@ -2603,16 +2897,7 @@ public class Jalview2XML
       ex.printStackTrace(System.err);
       if (attemptversion1parse)
       {
-        // Is Version 1 Jar file?
-        try
-        {
-          af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
-        } catch (Exception ex2)
-        {
-          System.err.println("Exception whilst loading as jalviewXMLV1:");
-          ex2.printStackTrace();
-          af = null;
-        }
+        // used to attempt to parse as V1 castor-generated xml
       }
       if (Desktop.instance != null)
       {
@@ -2732,8 +3017,8 @@ public class Jalview2XML
       if (!addedToSplitFrames.contains(af))
       {
         Viewport view = candidate.getKey();
-        Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
-                view.getHeight());
+        Desktop.addInternalFrame(af, view.getTitle(),
+                safeInt(view.getWidth()), safeInt(view.getHeight()));
         af.setMenusForViewport();
         System.err.println("Failed to restore view " + view.getTitle()
                 + " to split frame");
@@ -2990,6 +3275,28 @@ public class Jalview2XML
             : null;
 
     // ////////////////////////////////
+    // INITIALISE ALIGNMENT SEQUENCESETID AND VIEWID
+    //
+    //
+    // If we just load in the same jar file again, the sequenceSetId
+    // will be the same, and we end up with multiple references
+    // to the same sequenceSet. We must modify this id on load
+    // so that each load of the file gives a unique id
+
+    /**
+     * used to resolve correct alignment dataset for alignments with multiple
+     * views
+     */
+    String uniqueSeqSetId = null;
+    String viewId = null;
+    if (view != null)
+    {
+      uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
+      viewId = (view.getId() == null ? null
+              : view.getId() + uniqueSetSuffix);
+    }
+
+    // ////////////////////////////////
     // LOAD SEQUENCES
 
     List<SequenceI> hiddenSeqs = null;
@@ -3057,8 +3364,7 @@ public class Jalview2XML
         vi++;
       }
 
-      if (jseq.isViewreference() != null
-              && jseq.isViewreference().booleanValue())
+      if (safeBoolean(jseq.isViewreference()))
       {
         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
       }
@@ -3111,7 +3417,7 @@ public class Jalview2XML
 
       // finally, verify all data in vamsasSet is actually present in al
       // passing on flag indicating if it is actually a stored dataset
-      recoverDatasetFor(vamsasSet, al, isdsal);
+      recoverDatasetFor(vamsasSet, al, isdsal, uniqueSeqSetId);
     }
 
     if (referenceseqForView != null)
@@ -3150,7 +3456,7 @@ public class Jalview2XML
             Feature feat = features.get(f);
             SequenceFeature sf = new SequenceFeature(feat.getType(),
                     feat.getDescription(), feat.getBegin(), feat.getEnd(),
-                    feat.getScore(), feat.getFeatureGroup());
+                    safeFloat(feat.getScore()), feat.getFeatureGroup());
             sf.setStatus(feat.getStatus());
 
             /*
@@ -3344,22 +3650,21 @@ public class Jalview2XML
           // annotation.setAutoCalculated(true);
           // }
         }
-        if (autoForView || // (annotation.hasAutoCalculated() &&
-                annotation.isAutoCalculated())
+        if (autoForView || annotation.isAutoCalculated())
         {
           // remove ID - we don't recover annotation from other views for
           // view-specific annotation
           annotation.setId(null);
         }
 
-        // set visiblity for other annotation in this view
+        // set visibility for other annotation in this view
         String annotationId = annotation.getId();
         if (annotationId != null && annotationIds.containsKey(annotationId))
         {
           AlignmentAnnotation jda = annotationIds.get(annotationId);
           // in principle Visible should always be true for annotation displayed
           // in multiple views
-          if (annotation.isVisible() != null) // annotation.hasVisible())
+          if (annotation.isVisible() != null)
           {
             jda.visible = annotation.isVisible();
           }
@@ -3386,8 +3691,7 @@ public class Jalview2XML
               continue;
             }
 
-            float value = annElement.getValue() == null ? 0f
-                    : annElement.getValue().floatValue();
+            float value = safeFloat(annElement.getValue());
             anot[anpos] = new jalview.datamodel.Annotation(
                     annElement.getDisplayCharacter(),
                     annElement.getDescription(),
@@ -3399,16 +3703,7 @@ public class Jalview2XML
                                                     .getSecondaryStructure()
                                                     .charAt(0),
                     value);
-            // JBPNote: Consider verifying dataflow for IO of secondary
-            // structure annotation read from Stockholm files
-            // this was added to try to ensure that
-            // if (anot[ annElement.getPosition()].secondaryStructure>' ')
-            // {
-            // anot[ annElement.getPosition()].displayCharacter = "";
-            // }
-            final int colourValue = annElement.getColour() == null ? 0
-                    : annElement.getColour().intValue();
-            anot[anpos].colour = new java.awt.Color(colourValue);
+            anot[anpos].colour = new Color(safeInt(annElement.getColour()));
             if (firstColour == null)
             {
               firstColour = anot[anpos].colour;
@@ -3425,18 +3720,17 @@ public class Jalview2XML
           // }
           jaa = new jalview.datamodel.AlignmentAnnotation(
                   annotation.getLabel(), annotation.getDescription(), anot,
-                  llim, hlim, annotation.getGraphType());
+                  llim, hlim, safeInt(annotation.getGraphType()));
 
-          jaa.graphGroup = annotation.getGraphGroup();
+          jaa.graphGroup = safeInt(annotation.getGraphGroup());
           jaa._linecolour = firstColour;
           if (annotation.getThresholdLine() != null)
           {
             jaa.setThreshold(new jalview.datamodel.GraphLine(
-                    annotation.getThresholdLine().getValue(),
+                    safeFloat(annotation.getThresholdLine().getValue()),
                     annotation.getThresholdLine().getLabel(),
-                    new java.awt.Color(
-                            annotation.getThresholdLine().getColour())));
-
+                    new java.awt.Color(safeInt(
+                            annotation.getThresholdLine().getColour()))));
           }
           if (autoForView || annotation.isAutoCalculated())
           {
@@ -3490,7 +3784,7 @@ public class Jalview2XML
 
         if (annotation.getScore() != null)
         {
-          jaa.setScore(annotation.getScore());
+          jaa.setScore(annotation.getScore().doubleValue());
         }
         if (annotation.isVisible() != null)
         {
@@ -3507,8 +3801,7 @@ public class Jalview2XML
         {
           jaa.scaleColLabel = annotation.isScaleColLabels().booleanValue();
         }
-        if (/*annotation.hasAutoCalculated() && */annotation
-                .isAutoCalculated())
+        if (annotation.isAutoCalculated())
         {
           // newer files have an 'autoCalculated' flag and store calculation
           // state in viewport properties
@@ -3519,11 +3812,7 @@ public class Jalview2XML
         {
           jaa.graphHeight = annotation.getGraphHeight().intValue();
         }
-        // if (annotation.hasBelowAlignment())
-        // {
-        // schema specifies default for optional attribute
-          jaa.belowAlignment = annotation.isBelowAlignment();
-        // }
+        jaa.belowAlignment = annotation.isBelowAlignment();
         jaa.setCalcId(annotation.getCalcId());
         if (annotation.getProperty().size() > 0)
         {
@@ -3570,12 +3859,11 @@ public class Jalview2XML
           }
           else
           {
-            cs = ColourSchemeProperty.getColourScheme(al,
+            cs = ColourSchemeProperty.getColourScheme(null, al,
                     jGroup.getColour());
           }
         }
-        int pidThreshold = jGroup.getPidThreshold() == null ? 0
-                : jGroup.getPidThreshold().intValue();
+        int pidThreshold = safeInt(jGroup.getPidThreshold());
 
         Vector<SequenceI> seqs = new Vector<>();
 
@@ -3596,40 +3884,24 @@ public class Jalview2XML
         }
 
         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
-                jGroup.isDisplayBoxes(), jGroup.isDisplayText(),
-                jGroup.isColourText(), jGroup.getStart(), jGroup.getEnd());
+                safeBoolean(jGroup.isDisplayBoxes()),
+                safeBoolean(jGroup.isDisplayText()),
+                safeBoolean(jGroup.isColourText()),
+                safeInt(jGroup.getStart()), safeInt(jGroup.getEnd()));
         sg.getGroupColourScheme().setThreshold(pidThreshold, true);
         sg.getGroupColourScheme()
-                .setConservationInc(jGroup.getConsThreshold() == null ? 0
-                        : jGroup.getConsThreshold().intValue());
-        sg.setOutlineColour(
-                new java.awt.Color(jGroup.getOutlineColour() == null ? 0
-                        : jGroup.getOutlineColour().intValue()));
-
-        sg.textColour = new java.awt.Color(jGroup.getTextCol1());
-        sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
-        sg.setShowNonconserved(
-                jGroup.isShowUnconserved() != null
-                        ? jGroup.isShowUnconserved().booleanValue()
-                        : false);
-        sg.thresholdTextColour = jGroup.getTextColThreshold();
+                .setConservationInc(safeInt(jGroup.getConsThreshold()));
+        sg.setOutlineColour(new Color(safeInt(jGroup.getOutlineColour())));
+
+        sg.textColour = new Color(safeInt(jGroup.getTextCol1()));
+        sg.textColour2 = new Color(safeInt(jGroup.getTextCol2()));
+        sg.setShowNonconserved(safeBoolean(jGroup.isShowUnconserved()));
+        sg.thresholdTextColour = safeInt(jGroup.getTextColThreshold());
         // attributes with a default in the schema are never null
-        // if (jGroup.hasShowConsensusHistogram())
-        // {
           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
-        // }
-        // if (jGroup.hasShowSequenceLogo())
-        // {
           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
-        // }
-        // if (jGroup.hasNormaliseSequenceLogo())
-        // {
           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
-        // }
-        // if (jGroup.hasIgnoreGapsinConsensus())
-        // {
         sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus());
-        // }
         if (jGroup.getConsThreshold() != null
                 && jGroup.getConsThreshold().intValue() != 0)
         {
@@ -3685,13 +3957,6 @@ public class Jalview2XML
     // ///////////////////////////////
     // LOAD VIEWPORT
 
-    // If we just load in the same jar file again, the sequenceSetId
-    // will be the same, and we end up with multiple references
-    // to the same sequenceSet. We must modify this id on load
-    // so that each load of the file gives a unique id
-    String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
-    String viewId = (view.getId() == null ? null
-            : view.getId() + uniqueSetSuffix);
     AlignFrame af = null;
     AlignViewport av = null;
     // now check to see if we really need to create a new viewport.
@@ -3773,6 +4038,7 @@ public class Jalview2XML
     if (loadTreesAndStructures)
     {
       loadTrees(jalviewModel, view, af, av, ap);
+      loadPCAViewers(jalviewModel, ap);
       loadPDBStructures(jprovider, jseqs, af, ap);
       loadRnaViewers(jprovider, jseqs, ap);
     }
@@ -3819,7 +4085,7 @@ public class Jalview2XML
            * add the structure to the Varna display (with session state copied
            * from the jar to a temporary file)
            */
-          boolean gapped = ss.isGapped();
+          boolean gapped = safeBoolean(ss.isGapped());
           String rnaTitle = ss.getTitle();
           String sessionState = ss.getViewerState();
           String tempStateFile = copyJarEntry(jprovider, sessionState,
@@ -3827,7 +4093,7 @@ public class Jalview2XML
           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
         }
-        appVarna.setInitialSelection(viewer.getSelectedRna());
+        appVarna.setInitialSelection(safeInt(viewer.getSelectedRna()));
       }
     }
   }
@@ -3868,8 +4134,9 @@ public class Jalview2XML
      * viewer not found - make it
      */
     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
-            viewer.getXpos(), viewer.getYpos(), viewer.getWidth(),
-            viewer.getHeight(), viewer.getDividerLocation());
+            safeInt(viewer.getXpos()), safeInt(viewer.getYpos()),
+            safeInt(viewer.getWidth()), safeInt(viewer.getHeight()),
+            safeInt(viewer.getDividerLocation()));
     AppVarna varna = new AppVarna(model, ap);
 
     return varna;
@@ -3898,10 +4165,10 @@ public class Jalview2XML
         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());
+          tp = af.showNewickTree(new NewickFile(tree.getNewick()),
+                  tree.getTitle(), safeInt(tree.getWidth()),
+                  safeInt(tree.getHeight()), safeInt(tree.getXpos()),
+                  safeInt(tree.getYpos()));
           if (tree.getId() != null)
           {
             // perhaps bind the tree id to something ?
@@ -3913,13 +4180,15 @@ public class Jalview2XML
           // 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.setViewport(av); // af.viewport; // TODO: verify 'associate with all
-          // views'
-          // works still
+          tp.setBounds(new Rectangle(safeInt(tree.getXpos()),
+                  safeInt(tree.getYpos()), safeInt(tree.getWidth()),
+                  safeInt(tree.getHeight())));
+          tp.setViewport(av); // af.viewport;
+          // TODO: verify 'associate with all views' works still
           tp.getTreeCanvas().setViewport(av); // af.viewport;
           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
+          // FIXME: should we use safeBoolean here ?
+          tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
 
         }
         if (tp == null)
@@ -3929,27 +4198,29 @@ public class Jalview2XML
           continue;
         }
 
-        tp.fitToWindow.setState(tree.isFitToWindow());
+        tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
         tp.fitToWindow_actionPerformed(null);
 
         if (tree.getFontName() != null)
         {
-          tp.setTreeFont(new java.awt.Font(tree.getFontName(),
-                  tree.getFontStyle(), tree.getFontSize()));
+          tp.setTreeFont(
+                  new Font(tree.getFontName(), safeInt(tree.getFontStyle()),
+                          safeInt(tree.getFontSize())));
         }
         else
         {
-          tp.setTreeFont(new java.awt.Font(view.getFontName(),
-                  view.getFontStyle(), tree.getFontSize()));
+          tp.setTreeFont(
+                  new Font(view.getFontName(), safeInt(view.getFontStyle()),
+                          safeInt(view.getFontSize())));
         }
 
-        tp.showPlaceholders(tree.isMarkUnlinked());
-        tp.showBootstrap(tree.isShowBootstrap());
-        tp.showDistances(tree.isShowDistances());
+        tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
+        tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
+        tp.showDistances(safeBoolean(tree.isShowDistances()));
 
-        tp.getTreeCanvas().setThreshold(tree.getThreshold());
+        tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
 
-        if (tree.isCurrentTree())
+        if (safeBoolean(tree.isCurrentTree()))
         {
           af.getViewport().setCurrentTree(tp.getTree());
         }
@@ -4003,10 +4274,10 @@ public class Jalview2XML
                     loadPDBFile(jprovider, pdbid.getId(), pdbid.getFile()));
             jpdb.setId(pdbid.getId());
 
-            int x = structureState.getXpos();
-            int y = structureState.getYpos();
-            int width = structureState.getWidth();
-            int height = structureState.getHeight();
+            int x = safeInt(structureState.getXpos());
+            int y = safeInt(structureState.getYpos());
+            int width = safeInt(structureState.getWidth());
+            int height = safeInt(structureState.getHeight());
 
             // Probably don't need to do this anymore...
             // Desktop.desktop.getComponentAt(x, y);
@@ -4569,17 +4840,17 @@ public class Jalview2XML
           String viewId, List<JvAnnotRow> autoAlan)
   {
     AlignFrame af = null;
-    af = new AlignFrame(al, view.getWidth(), view.getHeight(),
-            uniqueSeqSetId, viewId);
+    af = new AlignFrame(al, safeInt(view.getWidth()),
+            safeInt(view.getHeight()), uniqueSeqSetId, viewId);
 
     af.setFileName(file, FileFormat.Jalview);
 
     final AlignViewport viewport = af.getViewport();
     for (int i = 0; i < JSEQ.size(); i++)
     {
-      viewport.setSequenceColour(
-              viewport.getAlignment().getSequenceAt(i),
-              new java.awt.Color(JSEQ.get(i).getColour()));
+      int colour = safeInt(JSEQ.get(i).getColour());
+      viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
+              new Color(colour));
     }
 
     if (al.hasSeqrep())
@@ -4588,7 +4859,7 @@ public class Jalview2XML
       viewport.setDisplayReferenceSeq(true);
     }
 
-    viewport.setGatherViewsHere(view.isGatheredViews());
+    viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
 
     if (view.getSequenceSetId() != null)
     {
@@ -4640,49 +4911,48 @@ public class Jalview2XML
     }
     // recover view properties and display parameters
 
-    viewport.setShowAnnotation(view.isShowAnnotation());
-    viewport.setAbovePIDThreshold(view.isPidSelected());
-    final int pidThreshold = view.getPidThreshold() == null ? 0
-            : view.getPidThreshold().intValue();
+    viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
+    viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
+    final int pidThreshold = safeInt(view.getPidThreshold());
     viewport.setThreshold(pidThreshold);
 
-    viewport.setColourText(view.isShowColourText());
+    viewport.setColourText(safeBoolean(view.isShowColourText()));
 
     viewport
-            .setConservationSelected(view.isConservationSelected());
-    viewport.setIncrement(view.getConsThreshold() == null ? 0
-            : view.getConsThreshold().intValue());
-    viewport.setShowJVSuffix(view.isShowFullId());
-    viewport.setRightAlignIds(view.isRightAlignIds());
-    viewport.setFont(new java.awt.Font(view.getFontName(),
-            view.getFontStyle(), view.getFontSize()), true);
+            .setConservationSelected(
+                    safeBoolean(view.isConservationSelected()));
+    viewport.setIncrement(safeInt(view.getConsThreshold()));
+    viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
+    viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
+    viewport.setFont(new Font(view.getFontName(),
+            safeInt(view.getFontStyle()), safeInt(view.getFontSize())),
+            true);
     ViewStyleI vs = viewport.getViewStyle();
     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
     viewport.setViewStyle(vs);
     // TODO: allow custom charWidth/Heights to be restored by updating them
     // after setting font - which means set above to false
-    viewport.setRenderGaps(view.isRenderGaps());
-    viewport.setWrapAlignment(view.isWrapAlignment());
-    viewport.setShowAnnotation(view.isShowAnnotation());
+    viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
+    viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
+    viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
 
-    viewport.setShowBoxes(view.isShowBoxes());
+    viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
 
-    viewport.setShowText(view.isShowText());
+    viewport.setShowText(safeBoolean(view.isShowText()));
 
-    viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
-    viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
-    viewport.setThresholdTextColour(view.getTextColThreshold());
-    viewport.setShowUnconserved(
-            view.isShowUnconserved() ? view.isShowUnconserved() : false);
-    viewport.getRanges().setStartRes(view.getStartRes());
+    viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
+    viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
+    viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
+    viewport.setShowUnconserved(view.isShowUnconserved());
+    viewport.getRanges().setStartRes(safeInt(view.getStartRes()));
 
     if (view.getViewName() != null)
     {
       viewport.setViewName(view.getViewName());
       af.setInitialTabVisible();
     }
-    af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
-            view.getHeight());
+    af.setBounds(safeInt(view.getXpos()), safeInt(view.getYpos()),
+            safeInt(view.getWidth()), safeInt(view.getHeight()));
     // startSeq set in af.alignPanel.updateLayout below
     af.alignPanel.updateLayout();
     ColourSchemeI cs = null;
@@ -4703,91 +4973,44 @@ public class Jalview2XML
       }
       else
       {
-        cs = ColourSchemeProperty.getColourScheme(al, view.getBgColour());
+        cs = ColourSchemeProperty.getColourScheme(af.getViewport(), al,
+                view.getBgColour());
       }
     }
 
+    /*
+     * turn off 'alignment colour applies to all groups'
+     * while restoring global colour scheme
+     */
+    viewport.setColourAppliesToAllGroups(false);
     viewport.setGlobalColourScheme(cs);
     viewport.getResidueShading().setThreshold(pidThreshold,
             view.isIgnoreGapsinConsensus());
     viewport.getResidueShading()
             .setConsensus(viewport.getSequenceConsensusHash());
-    viewport.setColourAppliesToAllGroups(false);
-
-    if (view.isConservationSelected() && cs != null)
+    if (safeBoolean(view.isConservationSelected()) && cs != null)
     {
       viewport.getResidueShading()
-              .setConservationInc(view.getConsThreshold());
+              .setConservationInc(safeInt(view.getConsThreshold()));
     }
-
     af.changeColour(cs);
-
     viewport.setColourAppliesToAllGroups(true);
 
     viewport
-            .setShowSequenceFeatures(view.isShowSequenceFeatures());
+            .setShowSequenceFeatures(
+                    safeBoolean(view.isShowSequenceFeatures()));
 
-    // if (view.hasCentreColumnLabels())
-    // {
     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
-    // }
-    // if (view.hasIgnoreGapsinConsensus())
-    // {
     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
-    // }
-    // if (view.hasFollowHighlight())
-    // {
-
     viewport.setFollowHighlight(view.isFollowHighlight());
-    // }
-    // if (view.hasFollowSelection())
-    // {
     viewport.followSelection = view.isFollowSelection();
-    // }
-    // if (view.hasShowConsensusHistogram())
-    // {
     viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
-    // }
-    // else
-    // {
-    // viewport.setShowConsensusHistogram(true);
-    // }
-    // if (view.hasShowSequenceLogo())
-    // {
     viewport.setShowSequenceLogo(view.isShowSequenceLogo());
-    // }
-    // else
-    // {
-    // viewport.setShowSequenceLogo(false);
-    // }
-    // if (view.hasNormaliseSequenceLogo())
-    // {
     viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
-    // }
-    if (view.isShowDbRefTooltip() != null)
-    {
-      viewport.setShowDBRefs(view.isShowDbRefTooltip());
-    }
-    if (view.isShowNPfeatureTooltip() != null)
-    {
-      viewport.setShowNPFeats(view.isShowNPfeatureTooltip());
-    }
-    // if (view.hasShowGroupConsensus())
-    // {
+    viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
+    viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
-    // }
-    // else
-    // {
-    // viewport.setShowGroupConsensus(false);
-    // }
-    // if (view.hasShowGroupConservation())
-    // {
     viewport.setShowGroupConservation(view.isShowGroupConservation());
-    // }
-    // else
-    // {
-    // viewport.setShowGroupConservation(false);
-    // }
 
     // recover feature settings
     if (jm.getFeatureSettings() != null)
@@ -4815,7 +5038,7 @@ public class Jalview2XML
         if (filters != null)
         {
           FeatureMatcherSetI filter = Jalview2XML
-                  .unmarshalFilter(featureType, filters);
+                  .parseFilter(featureType, filters);
           if (!filter.isEmpty())
           {
             fr.setFeatureFilter(featureType, filter);
@@ -4843,13 +5066,11 @@ public class Jalview2XML
           {
             noValueColour = maxColour;
           }
-          float min = setting.getMin() != null
-                  ? setting.getMin().floatValue()
-                  : 0f;
-          float max = setting.getMin() != null
-                  ? setting.getMax().floatValue()
-                  : 1f;
-          FeatureColourI gc = new FeatureColour(minColour, maxColour,
+          float min = safeFloat(safeFloat(setting.getMin()));
+          float max = setting.getMax() == null ? 1f
+                  : setting.getMax().floatValue();
+          FeatureColourI gc = new FeatureColour(maxColour, minColour,
+                  maxColour,
                   noValueColour, min, max);
           if (setting.getAttributeName().size() > 0)
           {
@@ -4859,7 +5080,7 @@ public class Jalview2XML
           if (setting.getThreshold() != null)
           {
             gc.setThreshold(setting.getThreshold().floatValue());
-            int threshstate = setting.getThreshstate().intValue();
+            int threshstate = safeInt(setting.getThreshstate());
             // -1 = None, 0 = Below, 1 = Above threshold
             if (threshstate == 0)
             {
@@ -4897,7 +5118,7 @@ public class Jalview2XML
           featureOrder.put(featureType, new Float(
                   fs / jm.getFeatureSettings().getSetting().size()));
         }
-        if (setting.isDisplay())
+        if (safeBoolean(setting.isDisplay()))
         {
           fdi.setVisible(featureType);
         }
@@ -4920,9 +5141,9 @@ public class Jalview2XML
     {
       for (int c = 0; c < view.getHiddenColumns().size(); c++)
       {
-        viewport.hideColumns(view.getHiddenColumns().get(c).getStart(),
-                view.getHiddenColumns().get(c).getEnd() // +1
-        );
+        final HiddenColumns hc = view.getHiddenColumns().get(c);
+        viewport.hideColumns(safeInt(hc.getStart()),
+                safeInt(hc.getEnd()) /* +1 */);
       }
     }
     if (view.getCalcIdParam() != null)
@@ -4953,8 +5174,8 @@ public class Jalview2XML
     String complementaryViewId = view.getComplementId();
     if (complementaryViewId == null)
     {
-      Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
-              view.getHeight());
+      Desktop.addInternalFrame(af, view.getTitle(),
+              safeInt(view.getWidth()), safeInt(view.getHeight()));
       // recompute any autoannotation
       af.alignPanel.updateAnnotation(false, true);
       reorderAutoannotation(af, al, autoAlan);
@@ -5029,34 +5250,36 @@ public class Jalview2XML
     }
     if (matchedAnnotation.getThreshold() == null)
     {
-      matchedAnnotation.setThreshold(new GraphLine(
-              viewAnnColour.getThreshold(), "Threshold", Color.black));
+      matchedAnnotation.setThreshold(
+              new GraphLine(safeFloat(viewAnnColour.getThreshold()),
+                      "Threshold", Color.black));
     }
 
     AnnotationColourGradient cs = null;
     if (viewAnnColour.getColourScheme().equals("None"))
     {
       cs = new AnnotationColourGradient(matchedAnnotation,
-              new Color(viewAnnColour.getMinColour()),
-              new Color(viewAnnColour.getMaxColour()),
-              viewAnnColour.getAboveThreshold());
+              new Color(safeInt(viewAnnColour.getMinColour())),
+              new Color(safeInt(viewAnnColour.getMaxColour())),
+              safeInt(viewAnnColour.getAboveThreshold()));
     }
     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
     {
       cs = new AnnotationColourGradient(matchedAnnotation,
               getUserColourScheme(model, viewAnnColour.getColourScheme()),
-              viewAnnColour.getAboveThreshold());
+              safeInt(viewAnnColour.getAboveThreshold()));
     }
     else
     {
       cs = new AnnotationColourGradient(matchedAnnotation,
-              ColourSchemeProperty.getColourScheme(al,
+              ColourSchemeProperty.getColourScheme(af.getViewport(), al,
                       viewAnnColour.getColourScheme()),
-              viewAnnColour.getAboveThreshold());
+              safeInt(viewAnnColour.getAboveThreshold()));
     }
 
-    boolean perSequenceOnly = viewAnnColour.isPerSequence();
-    boolean useOriginalColours = viewAnnColour.isPredefinedColours();
+    boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
+    boolean useOriginalColours = safeBoolean(
+            viewAnnColour.isPredefinedColours());
     cs.setSeqAssociated(perSequenceOnly);
     cs.setPredefinedColours(useOriginalColours);
 
@@ -5073,7 +5296,7 @@ public class Jalview2XML
 
         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
                 matchedAnnotation, sg.getColourScheme(),
-                viewAnnColour.getAboveThreshold());
+                safeInt(viewAnnColour.getAboveThreshold()));
         sg.setColourScheme(groupScheme);
         groupScheme.setSeqAssociated(perSequenceOnly);
         groupScheme.setPredefinedColours(useOriginalColours);
@@ -5238,13 +5461,51 @@ public class Jalview2XML
   }
 
   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
-          boolean ignoreUnrefed)
+          boolean ignoreUnrefed, String uniqueSeqSetId)
   {
     jalview.datamodel.AlignmentI ds = getDatasetFor(
             vamsasSet.getDatasetId());
+    AlignmentI xtant_ds = ds;
+    if (xtant_ds == null)
+    {
+      // good chance we are about to create a new dataset, but check if we've
+      // seen some of the dataset sequence IDs before.
+      // TODO: skip this check if we are working with project generated by
+      // version 2.11 or later
+      xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
+      if (xtant_ds != null)
+      {
+        ds = xtant_ds;
+        addDatasetRef(vamsasSet.getDatasetId(), ds);
+      }
+    }
     Vector dseqs = null;
+    if (!ignoreUnrefed)
+    {
+      // recovering an alignment View
+      AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
+      if (seqSetDS != null)
+      {
+        if (ds != null && ds != seqSetDS)
+        {
+          warn("JAL-3171 regression: Overwriting a dataset reference for an alignment"
+                  + " - CDS/Protein crossreference data may be lost");
+          if (xtant_ds != null)
+          {
+            // This can only happen if the unique sequence set ID was bound to a
+            // dataset that did not contain any of the sequences in the view
+            // currently being restored.
+            warn("JAL-3171 SERIOUS!  TOTAL CONFUSION - please consider contacting the Jalview Development team so they can investigate why your project caused this message to be displayed.");
+          }
+        }
+        ds = seqSetDS;
+        addDatasetRef(vamsasSet.getDatasetId(), ds);
+      }
+    }
     if (ds == null)
     {
+      // try even harder to restore dataset
+      AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
       // create a list of new dataset sequences
       dseqs = new Vector();
     }
@@ -5267,10 +5528,58 @@ public class Jalview2XML
     if (al.getDataset() == null && !ignoreUnrefed)
     {
       al.setDataset(ds);
+      // register dataset for the alignment's uniqueSeqSetId for legacy projects
+      addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
+    }
+    updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
+  }
+
+  /**
+   * XML dataset sequence ID to materialised dataset reference
+   */
+  HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
+
+  /**
+   * @return the first materialised dataset reference containing a dataset
+   *         sequence referenced in the given view
+   * @param list
+   *          - sequences from the view
+   */
+  AlignmentI checkIfHasDataset(List<Sequence> list)
+  {
+    for (Sequence restoredSeq : list)
+    {
+      AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
+      if (datasetFor != null)
+      {
+        return datasetFor;
+      }
     }
+    return null;
   }
 
   /**
+   * Register ds as the containing dataset for the dataset sequences referenced
+   * by sequences in list
+   * 
+   * @param list
+   *          - sequences in a view
+   * @param ds
+   */
+  void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
+  {
+    for (Sequence restoredSeq : list)
+    {
+      AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
+      if (prevDS != null && prevDS != ds)
+      {
+        warn("Dataset sequence appears in many datasets: "
+                + restoredSeq.getDsseqid());
+        // TODO: try to merge!
+      }
+    }
+  }
+  /**
    * 
    * @param vamsasSeq
    *          sequence definition to create/merge dataset sequence for
@@ -5529,15 +5838,16 @@ public class Jalview2XML
     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
             fto, m.getMapFromUnit().intValue(),
             m.getMapToUnit().intValue());
-    // if (m.getMappingChoice() != null)
-    // {
-    // MappingChoice mc = m.getMappingChoice();
+
+    /*
+     * (optional) choice of dseqFor or Sequence
+     */
     if (m.getDseqFor() != null)
     {
       String dsfor = m.getDseqFor();
       if (seqRefIds.containsKey(dsfor))
       {
-        /**
+        /*
          * recover from hash
          */
         jmap.setTo(seqRefIds.get(dsfor));
@@ -5547,9 +5857,9 @@ public class Jalview2XML
         frefedSequence.add(newMappingRef(dsfor, jmap));
       }
     }
-    else
+    else if (m.getSequence() != null)
     {
-      /**
+      /*
        * local sequence definition
        */
       Sequence ms = m.getSequence();
@@ -5606,6 +5916,10 @@ public class Jalview2XML
     initSeqRefs();
     JalviewModel jm = saveState(ap, null, null, null);
 
+    addDatasetRef(
+            jm.getVamsasModel().getSequenceSet().get(0).getDatasetId(),
+            ap.getAlignment().getDataset());
+
     uniqueSetSuffix = "";
     // jm.getJalviewModelSequence().getViewport(0).setId(null);
     jm.getViewport().get(0).setId(null);
@@ -5867,13 +6181,135 @@ public class Jalview2XML
   }
 
   /**
+   * Loads any saved PCA viewers
+   * 
+   * @param jms
+   * @param ap
+   */
+  protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap)
+  {
+    try
+    {
+      List<PcaViewer> pcaviewers = model.getPcaViewer();
+      for (PcaViewer viewer : pcaviewers)
+      {
+        String modelName = viewer.getScoreModelName();
+        SimilarityParamsI params = new SimilarityParams(
+                viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
+                viewer.isIncludeGaps(),
+                viewer.isDenominateByShortestLength());
+
+        /*
+         * create the panel (without computing the PCA)
+         */
+        PCAPanel panel = new PCAPanel(ap, modelName, params);
+
+        panel.setTitle(viewer.getTitle());
+        panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
+                viewer.getWidth(), viewer.getHeight()));
+
+        boolean showLabels = viewer.isShowLabels();
+        panel.setShowLabels(showLabels);
+        panel.getRotatableCanvas().setShowLabels(showLabels);
+        panel.getRotatableCanvas()
+                .setBgColour(new Color(viewer.getBgColour()));
+        panel.getRotatableCanvas()
+                .setApplyToAllViews(viewer.isLinkToAllViews());
+
+        /*
+         * load PCA output data
+         */
+        ScoreModelI scoreModel = ScoreModels.getInstance()
+                .getScoreModel(modelName, ap);
+        PCA pca = new PCA(null, scoreModel, params);
+        PcaDataType pcaData = viewer.getPcaData();
+
+        MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
+        pca.setPairwiseScores(pairwise);
+
+        MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
+        pca.setTridiagonal(triDiag);
+
+        MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
+        pca.setEigenmatrix(result);
+
+        panel.getPcaModel().setPCA(pca);
+
+        /*
+         * we haven't saved the input data! (JAL-2647 to do)
+         */
+        panel.setInputData(null);
+
+        /*
+         * add the sequence points for the PCA display
+         */
+        List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
+        for (SequencePoint sp : viewer.getSequencePoint())
+        {
+          String seqId = sp.getSequenceRef();
+          SequenceI seq = seqRefIds.get(seqId);
+          if (seq == null)
+          {
+            throw new IllegalStateException(
+                    "Unmatched seqref for PCA: " + seqId);
+          }
+          Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
+          jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
+                  seq, pt);
+          seqPoints.add(seqPoint);
+        }
+        panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
+
+        /*
+         * set min-max ranges and scale after setPoints (which recomputes them)
+         */
+        panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
+        SeqPointMin spMin = viewer.getSeqPointMin();
+        float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
+            spMin.getZPos() };
+        SeqPointMax spMax = viewer.getSeqPointMax();
+        float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
+            spMax.getZPos() };
+        panel.getRotatableCanvas().setSeqMinMax(min, max);
+
+        // todo: hold points list in PCAModel only
+        panel.getPcaModel().setSequencePoints(seqPoints);
+
+        panel.setSelectedDimensionIndex(viewer.getXDim(), X);
+        panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
+        panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
+
+        // is this duplication needed?
+        panel.setTop(seqPoints.size() - 1);
+        panel.getPcaModel().setTop(seqPoints.size() - 1);
+
+        /*
+         * add the axes' end points for the display
+         */
+        for (int i = 0; i < 3; i++)
+        {
+          Axis axis = viewer.getAxis().get(i);
+          panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
+                  axis.getXPos(), axis.getYPos(), axis.getZPos());
+        }
+
+        Desktop.addInternalFrame(panel, MessageManager.formatMessage(
+                "label.calc_title", "PCA", modelName), 475, 450);
+      }
+    } catch (Exception ex)
+    {
+      Cache.log.error("Error loading PCA: " + ex.toString());
+    }
+  }
+
+  /**
    * Populates an XML model of the feature colour scheme for one feature type
    * 
    * @param featureType
    * @param fcol
    * @return
    */
-  protected static Colour marshalColour(
+  public static Colour marshalColour(
           String featureType, FeatureColourI fcol)
   {
     Colour col = new Colour();
@@ -5930,7 +6366,7 @@ public class Jalview2XML
    * @param and
    *          if true, conditions are and-ed, else or-ed
    */
-  protected static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
+  public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
           FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
           boolean and)
   {
@@ -5996,14 +6432,14 @@ public class Jalview2XML
    * @param matcherSetModel
    * @return
    */
-  protected static FeatureMatcherSetI unmarshalFilter(
+  public static FeatureMatcherSetI parseFilter(
           String featureType,
           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
   {
     FeatureMatcherSetI result = new FeatureMatcherSet();
     try
     {
-      unmarshalFilterConditions(result, matcherSetModel, true);
+      parseFilterConditions(result, matcherSetModel, true);
     } catch (IllegalStateException e)
     {
       // mixing AND and OR conditions perhaps
@@ -6027,7 +6463,7 @@ public class Jalview2XML
    * @throws IllegalStateException
    *           if AND and OR conditions are mixed
    */
-  protected static void unmarshalFilterConditions(
+  protected static void parseFilterConditions(
           FeatureMatcherSetI matcherSet,
           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
           boolean and)
@@ -6080,13 +6516,12 @@ public class Jalview2XML
        * compound condition
        */
       List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
-              .getCompoundMatcher()
-              .getMatcherSet();
+              .getCompoundMatcher().getMatcherSet();
       boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
       if (matchers.size() == 2)
       {
-        unmarshalFilterConditions(matcherSet, matchers.get(0), anded);
-        unmarshalFilterConditions(matcherSet, matchers.get(1), anded);
+        parseFilterConditions(matcherSet, matchers.get(0), anded);
+        parseFilterConditions(matcherSet, matchers.get(1), anded);
       }
       else
       {
@@ -6101,7 +6536,7 @@ public class Jalview2XML
    * @param colourModel
    * @return
    */
-  protected static FeatureColourI unmarshalColour(Colour colourModel)
+  public static FeatureColourI parseColour(Colour colourModel)
   {
     FeatureColourI colour = null;
   
@@ -6130,9 +6565,9 @@ public class Jalview2XML
         noValueColour = maxcol;
       }
   
-      colour = new FeatureColour(mincol, maxcol, noValueColour,
-              colourModel.getMin(),
-              colourModel.getMax());
+      colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
+              safeFloat(colourModel.getMin()),
+              safeFloat(colourModel.getMax()));
       final List<String> attributeName = colourModel.getAttributeName();
       String[] attributes = attributeName
               .toArray(new String[attributeName.size()]);