Merge branch 'bug/JAL-3120restoreFeatureColour' into merge/JAL-3120
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 4 Mar 2019 10:54:27 +0000 (10:54 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 4 Mar 2019 10:54:27 +0000 (10:54 +0000)
Conflicts:
src/jalview/gui/Jalview2XML.java
test/jalview/io/Jalview2xmlTests.java

1  2 
src/jalview/gui/FeatureTypeSettings.java
src/jalview/project/Jalview2XML.java
src/jalview/schemes/FeatureColour.java
src/jalview/workers/ConsensusThread.java
test/jalview/gui/AlignFrameTest.java
test/jalview/io/FeaturesFileTest.java
test/jalview/project/Jalview2xmlTests.java
test/jalview/renderer/seqfeatures/FeatureRendererTest.java
test/jalview/schemes/FeatureColourTest.java

@@@ -242,7 -242,8 +242,7 @@@ public class FeatureTypeSettings extend
      String title = MessageManager
              .formatMessage("label.display_settings_for", new String[]
              { theType });
 -    initDialogFrame(this, true, false, title, 500, 500);
 -    
 +    initDialogFrame(this, true, false, title, 580, 500);
      waitForInput();
    }
  
                          : BELOW_THRESHOLD_OPTION);
          slider.setEnabled(true);
          slider.setValue((int) (fc.getThreshold() * scaleFactor));
 -        thresholdValue.setText(String.valueOf(getRoundedSliderValue()));
 +        thresholdValue.setText(String.valueOf(fc.getThreshold()));
          thresholdValue.setEnabled(true);
          thresholdIsMin.setEnabled(true);
        }
      maxColour.setBorder(new LineBorder(Color.black));
  
      /*
-      * default max colour to last plain colour;
-      * make min colour a pale version of max colour
+      * if not set, default max colour to last plain colour,
+      * and make min colour a pale version of max colour
       */
-     FeatureColourI fc = fr.getFeatureColours().get(featureType);
-     Color bg = fc.getColour() == null ? Color.BLACK : fc.getColour();
-     maxColour.setBackground(bg);
-     minColour.setBackground(ColorUtils.bleachColour(bg, 0.9f));
+     Color max = originalColour.getMaxColour();
+     if (max == null)
+     {
+       max = originalColour.getColour();
+       minColour.setBackground(ColorUtils.bleachColour(max, 0.9f));
+     }
+     else
+     {
+       maxColour.setBackground(max);
+       minColour.setBackground(originalColour.getMinColour());
+     }
  
      noValueCombo = new JComboBox<>();
      noValueCombo.addItem(MessageManager.getString("label.no_colour"));
          {
            thresholdValue
                    .setText(String.valueOf(slider.getValue() / scaleFactor));
 +          thresholdValue.setBackground(Color.white); // to reset red for invalid
            sliderValueChanged();
          }
        }
      singleColour.setFont(JvSwingUtils.getLabelFont());
      singleColour.setBorder(BorderFactory.createLineBorder(Color.black));
      singleColour.setPreferredSize(new Dimension(40, 20));
-     if (originalColour.isGraduatedColour())
-     {
-       singleColour.setBackground(originalColour.getMaxColour());
-       singleColour.setForeground(originalColour.getMaxColour());
-     }
-     else
-     {
+     // if (originalColour.isGraduatedColour())
+     // {
+     // singleColour.setBackground(originalColour.getMaxColour());
+     // singleColour.setForeground(originalColour.getMaxColour());
+     // }
+     // else
+     // {
        singleColour.setBackground(originalColour.getColour());
        singleColour.setForeground(originalColour.getColour());
-     }
+     // }
      singleColour.addMouseListener(new MouseAdapter()
      {
        @Override
    private FeatureColourI makeColourFromInputs()
    {
      /*
-      * easiest case - a single colour
+      * min-max range is to (or from) threshold value if 
+      * 'threshold is min/max' is selected 
       */
-     if (simpleColour.isSelected())
-     {
-       return new FeatureColour(singleColour.getBackground());
-     }
-     /*
-      * next easiest case - colour by Label, or attribute text
-      */
-     if (byCategory.isSelected())
-     {
-       Color c = singleColour.getBackground();
-       FeatureColourI fc = new FeatureColour(c);
-       fc.setColourByLabel(true);
-       String byWhat = (String) colourByTextCombo.getSelectedItem();
-       if (!LABEL_18N.equals(byWhat))
-       {
-         fc.setAttributeName(
-                 FeatureMatcher.fromAttributeDisplayName(byWhat));
-       }
-       return fc;
-     }
-     /*
-      * remaining case - graduated colour by score, or attribute value
-      */
-     Color noColour = null;
-     if (noValueCombo.getSelectedIndex() == MIN_COLOUR_OPTION)
-     {
-       noColour = minColour.getBackground();
-     }
-     else if (noValueCombo.getSelectedIndex() == MAX_COLOUR_OPTION)
-     {
-       noColour = maxColour.getBackground();
-     }
  
      float thresh = 0f;
      try
      {
        // invalid inputs are already handled on entry
      }
-     /*
-      * min-max range is to (or from) threshold value if 
-      * 'threshold is min/max' is selected 
-      */
      float minValue = min;
      float maxValue = max;
      final int thresholdOption = threshold.getSelectedIndex();
      {
        maxValue = thresh;
      }
+     Color noColour = null;
+     if (noValueCombo.getSelectedIndex() == MIN_COLOUR_OPTION)
+     {
+       noColour = minColour.getBackground();
+     }
+     else if (noValueCombo.getSelectedIndex() == MAX_COLOUR_OPTION)
+     {
+       noColour = maxColour.getBackground();
+     }
+     /*
+      * construct a colour that 'remembers' all the options, including
+      * those not currently selected
+      */
+     FeatureColourI fc = new FeatureColour(singleColour.getBackground(),
+             minColour.getBackground(), maxColour.getBackground(), noColour,
+             minValue, maxValue);
+     /*
+      * easiest case - a single colour
+      */
+     if (simpleColour.isSelected())
+     {
+       ((FeatureColour) fc).setGraduatedColour(false);
+       return fc;
+     }
  
      /*
-      * make the graduated colour
+      * next easiest case - colour by Label, or attribute text
       */
-     FeatureColourI fc = new FeatureColour(minColour.getBackground(),
-             maxColour.getBackground(), noColour, minValue, maxValue);
+     if (byCategory.isSelected())
+     {
+       fc.setColourByLabel(true);
+       String byWhat = (String) colourByTextCombo.getSelectedItem();
+       if (!LABEL_18N.equals(byWhat))
+       {
+         fc.setAttributeName(
+                 FeatureMatcher.fromAttributeDisplayName(byWhat));
+       }
+       return fc;
+     }
  
      /*
+      * remaining case - graduated colour by score, or attribute value;
       * set attribute to colour by if selected
       */
      String byWhat = (String) colourByRangeCombo.getSelectedItem();
    {
      try
      {
 +      /*
 +       * set 'adjusting' flag while moving the slider, so it 
 +       * doesn't then in turn change the value (with rounding)
 +       */
        adjusting = true;
        float f = Float.parseFloat(thresholdValue.getText());
 +      f = Float.max(f,  this.min);
 +      f = Float.min(f, this.max);
 +      thresholdValue.setText(String.valueOf(f));
        slider.setValue((int) (f * scaleFactor));
        threshline.value = f;
        thresholdValue.setBackground(Color.white); // ok
 -
 -      /*
 -       * force repaint of any Overview window or structure
 -       */
 -      ap.paintAlignment(true, true);
 +      adjusting = false;
 +      colourChanged(true);
      } catch (NumberFormatException ex)
      {
        thresholdValue.setBackground(Color.red); // not ok
 -    } finally
 -    {
        adjusting = false;
      }
    }
   * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
   * The Jalview Authors are detailed in the 'AUTHORS' file.
   */
 -package jalview.gui;
 +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;
@@@ -40,7 -31,6 +40,7 @@@ import jalview.datamodel.AlignmentAnnot
  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;
@@@ -52,29 -42,49 +52,29 @@@ import jalview.datamodel.features.Featu
  import jalview.datamodel.features.FeatureMatcherSet;
  import jalview.datamodel.features.FeatureMatcherSetI;
  import jalview.ext.varna.RnaModel;
 +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;
 +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.schemabinding.version2.AlcodMap;
 -import jalview.schemabinding.version2.AlcodonFrame;
 -import jalview.schemabinding.version2.Annotation;
 -import jalview.schemabinding.version2.AnnotationColours;
 -import jalview.schemabinding.version2.AnnotationElement;
 -import jalview.schemabinding.version2.CalcIdParam;
 -import jalview.schemabinding.version2.CompoundMatcher;
 -import jalview.schemabinding.version2.DBRef;
 -import jalview.schemabinding.version2.Features;
 -import jalview.schemabinding.version2.Group;
 -import jalview.schemabinding.version2.HiddenColumns;
 -import jalview.schemabinding.version2.JGroup;
 -import jalview.schemabinding.version2.JSeq;
 -import jalview.schemabinding.version2.JalviewModel;
 -import jalview.schemabinding.version2.JalviewModelSequence;
 -import jalview.schemabinding.version2.MapListFrom;
 -import jalview.schemabinding.version2.MapListTo;
 -import jalview.schemabinding.version2.Mapping;
 -import jalview.schemabinding.version2.MappingChoice;
 -import jalview.schemabinding.version2.MatchCondition;
 -import jalview.schemabinding.version2.MatcherSet;
 -import jalview.schemabinding.version2.OtherData;
 -import jalview.schemabinding.version2.PdbentryItem;
 -import jalview.schemabinding.version2.Pdbids;
 -import jalview.schemabinding.version2.Property;
 -import jalview.schemabinding.version2.RnaViewer;
 -import jalview.schemabinding.version2.SecondaryStructure;
 -import jalview.schemabinding.version2.Sequence;
 -import jalview.schemabinding.version2.SequenceSet;
 -import jalview.schemabinding.version2.SequenceSetProperties;
 -import jalview.schemabinding.version2.Setting;
 -import jalview.schemabinding.version2.StructureState;
 -import jalview.schemabinding.version2.ThresholdLine;
 -import jalview.schemabinding.version2.Tree;
 -import jalview.schemabinding.version2.UserColours;
 -import jalview.schemabinding.version2.Viewport;
 -import jalview.schemabinding.version2.types.ColourThreshTypeType;
 -import jalview.schemabinding.version2.types.FeatureMatcherByType;
 -import jalview.schemabinding.version2.types.NoValueColour;
  import jalview.schemes.AnnotationColourGradient;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ColourSchemeProperty;
@@@ -90,7 -100,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;
@@@ -100,56 -109,8 +100,56 @@@ import jalview.ws.jws2.jabaws2.Jws2Inst
  import jalview.ws.params.ArgumentI;
  import jalview.ws.params.AutoCalcSetting;
  import jalview.ws.params.WsParamSetI;
 +import jalview.xml.binding.jalview.AlcodonFrame;
 +import jalview.xml.binding.jalview.AlcodonFrame.AlcodMap;
 +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;
 +import jalview.xml.binding.jalview.FilterBy;
 +import jalview.xml.binding.jalview.JalviewModel;
 +import jalview.xml.binding.jalview.JalviewModel.FeatureSettings;
 +import jalview.xml.binding.jalview.JalviewModel.FeatureSettings.Group;
 +import jalview.xml.binding.jalview.JalviewModel.FeatureSettings.Setting;
 +import jalview.xml.binding.jalview.JalviewModel.JGroup;
 +import jalview.xml.binding.jalview.JalviewModel.JSeq;
 +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;
 +import jalview.xml.binding.jalview.JalviewModel.Viewport.CalcIdParam;
 +import jalview.xml.binding.jalview.JalviewModel.Viewport.HiddenColumns;
 +import jalview.xml.binding.jalview.JalviewUserColours;
 +import jalview.xml.binding.jalview.JalviewUserColours.Colour;
 +import jalview.xml.binding.jalview.MapListType.MapListFrom;
 +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;
 +import jalview.xml.binding.jalview.SequenceSet;
 +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.Font;
  import java.awt.Rectangle;
  import java.io.BufferedReader;
  import java.io.DataInputStream;
@@@ -162,14 -123,12 +162,14 @@@ import java.io.InputStreamReader
  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;
@@@ -187,14 -146,9 +187,14 @@@ import java.util.jar.JarOutputStream
  
  import javax.swing.JInternalFrame;
  import javax.swing.SwingUtilities;
 -
 -import org.exolab.castor.xml.Marshaller;
 -import org.exolab.castor.xml.Unmarshaller;
 +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.
@@@ -214,12 -168,6 +214,12 @@@ public class Jalview2XM
  
    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;
  
    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
    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
  
          String shortName = makeFilename(af, shortNames);
  
 -        int ap, apSize = af.alignPanels.size();
 +        int apSize = af.getAlignPanels().size();
  
 -        for (ap = 0; ap < apSize; ap++)
 +        for (int ap = 0; ap < apSize; ap++)
          {
 -          AlignmentPanel apanel = af.alignPanels.get(ap);
 +          AlignmentPanel apanel = (AlignmentPanel) af.getAlignPanels()
 +                  .get(ap);
            String fileName = apSize == 1 ? shortName : ap + shortName;
            if (!fileName.endsWith(".xml"))
            {
    {
      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<>();
  
        }
        ;
        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";
      AlignViewport av = ap.av;
      ViewportRanges vpRanges = av.getRanges();
  
 -    JalviewModel object = new JalviewModel();
 -    object.setVamsasModel(new jalview.schemabinding.version2.VamsasModel());
 +    final ObjectFactory objectFactory = new ObjectFactory();
 +    JalviewModel object = objectFactory.createJalviewModel();
 +    object.setVamsasModel(new VAMSAS());
  
 -    object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
 +    // object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
 +    try
 +    {
 +      GregorianCalendar c = new GregorianCalendar();
 +      DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
 +      XMLGregorianCalendar now = datatypeFactory.newXMLGregorianCalendar(c);// gregorianCalendar);
 +      object.setCreationDate(now);
 +    } catch (DatatypeConfigurationException e)
 +    {
 +      System.err.println("error writing date: " + e.toString());
 +    }
      object.setVersion(
              jalview.bin.Cache.getDefault("VERSION", "Development Build"));
  
  
      SequenceSet vamsasSet = new SequenceSet();
      Sequence vamsasSeq;
 -    JalviewModelSequence jms = new JalviewModelSequence();
 +    // JalviewModelSequence jms = new JalviewModelSequence();
  
      vamsasSet.setGapChar(jal.getGapCharacter() + "");
  
          SequenceSetProperties ssp = new SequenceSetProperties();
          ssp.setKey(key);
          ssp.setValue(jal.getProperties().get(key).toString());
 -        vamsasSet.addSequenceSetProperties(ssp);
 +        // vamsasSet.addSequenceSetProperties(ssp);
 +        vamsasSet.getSequenceSetProperties().add(ssp);
        }
      }
  
          else
          {
            vamsasSeq = createVamsasSequence(id, jds);
 -          vamsasSet.addSequence(vamsasSeq);
 +//          vamsasSet.addSequence(vamsasSeq);
 +          vamsasSet.getSequence().add(vamsasSeq);
            vamsasSetIds.put(id, vamsasSeq);
            seqRefIds.put(id, jds);
          }
              {
                if (reps[h] != jds)
                {
 -                jseq.addHiddenSequences(rjal.findIndex(reps[h]));
 +                // jseq.addHiddenSequences(rjal.findIndex(reps[h]));
 +                jseq.getHiddenSequences().add(rjal.findIndex(reps[h]));
                }
              }
            }
  
        // TODO: omit sequence features from each alignment view's XML dump if we
        // are storing dataset
 -      List<jalview.datamodel.SequenceFeature> sfs = jds
 -              .getSequenceFeatures();
 +      List<SequenceFeature> sfs = jds.getSequenceFeatures();
        for (SequenceFeature sf : sfs)
        {
 -        Features features = new Features();
 +        // Features features = new Features();
 +        Feature features = new Feature();
  
          features.setBegin(sf.getBegin());
          features.setEnd(sf.getEnd());
              OtherData keyValue = new OtherData();
              keyValue.setKey("LINK_" + l);
              keyValue.setValue(sf.links.elementAt(l).toString());
 -            features.addOtherData(keyValue);
 +            // features.addOtherData(keyValue);
 +            features.getOtherData().add(keyValue);
            }
          }
          if (sf.otherDetails != null)
                  otherData.setKey(key);
                  otherData.setKey2(subAttribute.getKey());
                  otherData.setValue(subAttribute.getValue().toString());
 -                features.addOtherData(otherData);
 +                // features.addOtherData(otherData);
 +                features.getOtherData().add(otherData);
                }
              }
              else
                OtherData otherData = new OtherData();
                otherData.setKey(key);
                otherData.setValue(value.toString());
 -              features.addOtherData(otherData);
 +              // features.addOtherData(otherData);
 +              features.getOtherData().add(otherData);
              }
            }
          }
  
 -        jseq.addFeatures(features);
 +        // jseq.addFeatures(features);
 +        jseq.getFeatures().add(features);
        }
  
        if (jdatasq.getAllPDBEntries() != null)
        {
 -        Enumeration en = jdatasq.getAllPDBEntries().elements();
 +        Enumeration<PDBEntry> en = jdatasq.getAllPDBEntries().elements();
          while (en.hasMoreElements())
          {
            Pdbids pdb = new Pdbids();
 -          jalview.datamodel.PDBEntry entry = (jalview.datamodel.PDBEntry) en
 -                  .nextElement();
 +          jalview.datamodel.PDBEntry entry = en.nextElement();
  
            String pdbId = entry.getId();
            pdb.setId(pdbId);
            Enumeration<String> props = entry.getProperties();
            if (props.hasMoreElements())
            {
 -            PdbentryItem item = new PdbentryItem();
 +            // PdbentryItem item = new PdbentryItem();
              while (props.hasMoreElements())
              {
                Property prop = new Property();
                String key = props.nextElement();
                prop.setName(key);
                prop.setValue(entry.getProperty(key).toString());
 -              item.addProperty(prop);
 +              // item.addProperty(prop);
 +              pdb.getProperty().add(prop);
              }
 -            pdb.addPdbentryItem(item);
 +            // pdb.addPdbentryItem(item);
            }
  
 -          jseq.addPdbids(pdb);
 +          // jseq.addPdbids(pdb);
 +          jseq.getPdbids().add(pdb);
          }
        }
  
        saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
  
 -      jms.addJSeq(jseq);
 +      // jms.addJSeq(jseq);
 +      object.getJSeq().add(jseq);
      }
  
      if (!storeDS && av.hasHiddenRows())
              alcmap.setDnasq(seqHash(dnas[m]));
              alcmap.setMapping(
                      createVamsasMapping(pmaps[m], dnas[m], null, false));
 -            alc.addAlcodMap(alcmap);
 +            // alc.addAlcodMap(alcmap);
 +            alc.getAlcodMap().add(alcmap);
              hasMap = true;
            }
            if (hasMap)
            {
 -            vamsasSet.addAlcodonFrame(alc);
 +            // vamsasSet.addAlcodonFrame(alc);
 +            vamsasSet.getAlcodonFrame().add(alc);
            }
          }
          // TODO: delete this ? dead code from 2.8.3->2.9 ?
            {
              TreePanel tp = (TreePanel) frames[t];
  
 -            if (tp.treeCanvas.av.getAlignment() == jal)
 +            if (tp.getTreeCanvas().getViewport().getAlignment() == jal)
              {
 -              Tree tree = new Tree();
 +              JalviewModel.Tree tree = new JalviewModel.Tree();
                tree.setTitle(tp.getTitle());
                tree.setCurrentTree((av.getCurrentTree() == tp.getTree()));
                tree.setNewick(tp.getTree().print());
 -              tree.setThreshold(tp.treeCanvas.threshold);
 +              tree.setThreshold(tp.getTreeCanvas().getThreshold());
  
                tree.setFitToWindow(tp.fitToWindow.getState());
                tree.setFontName(tp.getTreeFont().getName());
                tree.setXpos(tp.getX());
                tree.setYpos(tp.getY());
                tree.setId(makeHashCode(tp, null));
 -              jms.addTree(tree);
 +              tree.setLinkToAllViews(
 +                      tp.getTreeCanvas().isApplyToAllViews());
 +
 +              // jms.addTree(tree);
 +              object.getTree().add(tree);
              }
            }
          }
        }
      }
  
 +    /*
 +     * 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
              if (colourScheme instanceof jalview.schemes.UserColourScheme)
              {
                jGroup.setColour(
 -                      setUserColourScheme(colourScheme, userColours, jms));
 +                      setUserColourScheme(colourScheme, userColours,
 +                              object));
              }
              else
              {
              jGroup.setColour("AnnotationColourGradient");
              jGroup.setAnnotationColours(constructAnnotationColours(
                      (jalview.schemes.AnnotationColourGradient) colourScheme,
 -                    userColours, jms));
 +                    userColours, object));
            }
            else if (colourScheme instanceof jalview.schemes.UserColourScheme)
            {
              jGroup.setColour(
 -                    setUserColourScheme(colourScheme, userColours, jms));
 +                    setUserColourScheme(colourScheme, userColours, object));
            }
            else
            {
          jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
          for (SequenceI seq : sg.getSequences())
          {
 -          jGroup.addSeq(seqHash(seq));
 +          // jGroup.addSeq(seqHash(seq));
 +          jGroup.getSeq().add(seqHash(seq));
          }
        }
  
 -      jms.setJGroup(groups);
 +      //jms.setJGroup(groups);
 +      Object group;
 +      for (JGroup grp : groups)
 +      {
 +        object.getJGroup().add(grp);
 +      }
      }
      if (!storeDS)
      {
        {
          view.setComplementId(av.getCodingComplement().getViewId());
        }
 -      view.setViewName(av.viewName);
 +      view.setViewName(av.getViewName());
        view.setGatheredViews(av.isGatherViewsHere());
  
        Rectangle size = ap.av.getExplodedGeometry();
        if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
        {
          view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
 -                userColours, jms));
 +                userColours, object));
        }
        else if (av
                .getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
        {
 -        AnnotationColours ac = constructAnnotationColours(
 +        AnnotationColourScheme ac = constructAnnotationColours(
                  (jalview.schemes.AnnotationColourGradient) av
                          .getGlobalColourScheme(),
 -                userColours, jms);
 +                userColours, object);
  
          view.setAnnotationColours(ac);
          view.setBgColour("AnnotationColourGradient");
            view.setConsThreshold(vcs.getConservationInc());
            if (cs instanceof jalview.schemes.UserColourScheme)
            {
 -            view.setBgColour(setUserColourScheme(cs, userColours, jms));
 +            view.setBgColour(setUserColourScheme(cs, userColours, object));
            }
          }
          view.setPidThreshold(vcs.getThreshold());
  
        view.setConservationSelected(av.getConservationSelected());
        view.setPidSelected(av.getAbovePIDThreshold());
 -      view.setFontName(av.font.getName());
 -      view.setFontSize(av.font.getSize());
 -      view.setFontStyle(av.font.getStyle());
 +      final Font font = av.getFont();
 +      view.setFontName(font.getName());
 +      view.setFontSize(font.getSize());
 +      view.setFontStyle(font.getStyle());
        view.setScaleProteinAsCdna(av.getViewStyle().isScaleProteinAsCdna());
        view.setRenderGaps(av.isRenderGaps());
        view.setShowAnnotation(av.isShowAnnotation());
        view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
        if (av.getFeaturesDisplayed() != null)
        {
 -        jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings();
 +        FeatureSettings fs = new FeatureSettings();
  
          FeatureRenderer fr = ap.getSeqPanel().seqCanvas
                  .getFeatureRenderer();
          {
            for (String featureType : renderOrder)
            {
 -            Setting setting = new Setting();
 +            FeatureSettings.Setting setting = new FeatureSettings.Setting();
              setting.setType(featureType);
  
              /*
                setting.setColourByLabel(fcol.isColourByLabel());
                if (fcol.isColourByAttribute())
                {
 -                setting.setAttributeName(fcol.getAttributeName());
 +                String[] attName = fcol.getAttributeName();
 +                setting.getAttributeName().add(attName[0]);
 +                if (attName.length > 1)
 +                {
 +                  setting.getAttributeName().add(attName[1]);
 +                }
                }
                setting.setAutoScale(fcol.isAutoScaled());
                setting.setThreshold(fcol.getThreshold());
              {
                setting.setOrder(rorder);
              }
 -            fs.addSetting(setting);
 +            /// fs.addSetting(setting);
 +            fs.getSetting().add(setting);
              settingsAdded.addElement(featureType);
            }
          }
            g.setName(grp);
            g.setDisplay(((Boolean) fr.checkGroupVisibility(grp, false))
                            .booleanValue());
 -          fs.addGroup(g);
 +          // fs.addGroup(g);
 +          fs.getGroup().add(g);
            groupsAdded.addElement(grp);
          }
 -        jms.setFeatureSettings(fs);
 +        // jms.setFeatureSettings(fs);
 +        object.setFeatureSettings(fs);
        }
  
        if (av.hasHiddenColumns())
              HiddenColumns hc = new HiddenColumns();
              hc.setStart(region[0]);
              hc.setEnd(region[1]);
 -            view.addHiddenColumns(hc);
 +            // view.addHiddenColumns(hc);
 +            view.getHiddenColumns().add(hc);
            }
          }
        }
              // Some calcIds have no parameters.
              if (cidp != null)
              {
 -              view.addCalcIdParam(cidp);
 +              // view.addCalcIdParam(cidp);
 +              view.getCalcIdParam().add(cidp);
              }
            }
          }
        }
  
 -      jms.addViewport(view);
 +      // jms.addViewport(view);
 +      object.getViewport().add(view);
      }
 -    object.setJalviewModelSequence(jms);
 -    object.getVamsasModel().addSequenceSet(vamsasSet);
 +    // object.setJalviewModelSequence(jms);
 +    // object.getVamsasModel().addSequenceSet(vamsasSet);
 +    object.getVamsasModel().getSequenceSet().add(vamsasSet);
  
      if (jout != null && fileName != null)
      {
          jout.putNextEntry(entry);
          PrintWriter pout = new PrintWriter(
                  new OutputStreamWriter(jout, UTF_8));
 -        Marshaller marshaller = new Marshaller(pout);
 -        marshaller.marshal(object);
 +        JAXBContext jaxbContext = JAXBContext
 +                .newInstance(JalviewModel.class);
 +        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
 +
 +        // output pretty printed
 +        // jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
 +        jaxbMarshaller.marshal(
 +                new ObjectFactory().createJalviewModel(object), pout);
 +
 +        // jaxbMarshaller.marshal(object, pout);
 +        // marshaller.marshal(object);
          pout.flush();
          jout.closeEntry();
        } catch (Exception ex)
        {
          // TODO: raise error in GUI if marshalling failed.
 +        System.err.println("Error writing Jalview project");
          ex.printStackTrace();
        }
      }
    }
  
    /**
 +   * 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>
            rna.setHeight(varna.getHeight());
            rna.setDividerLocation(varna.getDividerLocation());
            rna.setSelectedRna(varna.getSelectedIndex());
 -          jseq.addRnaViewer(rna);
 +          // jseq.addRnaViewer(rna);
 +          jseq.getRnaViewer().add(rna);
  
            /*
             * Store each Varna panel's state once in the project per sequence.
                ss.setViewerState(jarEntryName);
                ss.setGapped(model.gapped);
                ss.setTitle(model.title);
 -              rna.addSecondaryStructure(ss);
 +              // rna.addSecondaryStructure(ss);
 +              rna.getSecondaryStructure().add(ss);
              }
            }
          }
            state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
            state.setColourByJmol(viewFrame.isColouredByViewer());
            state.setType(viewFrame.getViewerType().toString());
 -          pdb.addStructureState(state);
 +          // pdb.addStructureState(state);
 +          pdb.getStructureState().add(state);
          }
        }
      }
    }
  
    /**
 -   * Populates the AnnotationColours xml for save. This captures the settings of
 -   * the options in the 'Colour by Annotation' dialog.
 +   * Populates the AnnotationColourScheme xml for save. This captures the
 +   * settings of the options in the 'Colour by Annotation' dialog.
     * 
     * @param acg
     * @param userColours
 -   * @param jms
 +   * @param jm
     * @return
     */
 -  private AnnotationColours constructAnnotationColours(
 +  private AnnotationColourScheme constructAnnotationColours(
            AnnotationColourGradient acg, List<UserColourScheme> userColours,
 -          JalviewModelSequence jms)
 +          JalviewModel jm)
    {
 -    AnnotationColours ac = new AnnotationColours();
 +    AnnotationColourScheme ac = new AnnotationColourScheme();
      ac.setAboveThreshold(acg.getAboveThreshold());
      ac.setThreshold(acg.getAnnotationThreshold());
      // 2.10.2 save annotationId (unique) not annotation label
      if (acg.getBaseColour() instanceof UserColourScheme)
      {
        ac.setColourScheme(
 -              setUserColourScheme(acg.getBaseColour(), userColours, jms));
 +              setUserColourScheme(acg.getBaseColour(), userColours, jm));
      }
      else
      {
        {
          for (String pr : annotation.getProperties())
          {
 -          Property prop = new Property();
 +          jalview.xml.binding.jalview.Annotation.Property prop = new jalview.xml.binding.jalview.Annotation.Property();
            prop.setName(pr);
            prop.setValue(annotation.getProperty(pr));
 -          an.addProperty(prop);
 +          // an.addProperty(prop);
 +          an.getProperty().add(prop);
          }
        }
  
              ae.setColour(annotation.annotations[a].colour.getRGB());
            }
  
 -          an.addAnnotationElement(ae);
 +          // an.addAnnotationElement(ae);
 +          an.getAnnotationElement().add(ae);
            if (annotation.autoCalculated)
            {
              // only write one non-null entry into the annotation row -
        {
          // skip autocalculated annotation - these are only provided for
          // alignments
 -        vamsasSet.addAnnotation(an);
 +        // vamsasSet.addAnnotation(an);
 +        vamsasSet.getAnnotation().add(an);
        }
      }
  
      {
        CalcIdParam vCalcIdParam = new CalcIdParam();
        vCalcIdParam.setCalcId(calcId);
 -      vCalcIdParam.addServiceURL(settings.getServiceURI());
 +      // vCalcIdParam.addServiceURL(settings.getServiceURI());
 +      vCalcIdParam.getServiceURL().add(settings.getServiceURI());
        // generic URI allowing a third party to resolve another instance of the
        // service used for this calculation
 -      for (String urls : settings.getServiceURLs())
 +      for (String url : settings.getServiceURLs())
        {
 -        vCalcIdParam.addServiceURL(urls);
 +        // vCalcIdParam.addServiceURL(urls);
 +        vCalcIdParam.getServiceURL().add(url);
        }
        vCalcIdParam.setVersion("1.0");
        if (settings.getPreset() != null)
    {
      if (calcIdParam.getVersion().equals("1.0"))
      {
 +      final String[] calcIds = calcIdParam.getServiceURL().toArray(new String[0]);
        Jws2Instance service = Jws2Discoverer.getDiscoverer()
 -              .getPreferredServiceFor(calcIdParam.getServiceURL());
 +              .getPreferredServiceFor(calcIds);
        if (service != null)
        {
          WsParamSetI parmSet = null;
          {
            parmSet = service.getParamStore().parseServiceParameterFile(
                    calcIdParam.getName(), calcIdParam.getDescription(),
 -                  calcIdParam.getServiceURL(),
 +                  calcIds,
                    calcIdParam.getParameters().replace("|\\n|", "\n"));
          } catch (IOException x)
          {
                    jds, recurse);
            dbref.setMapping(mp);
          }
 -        vamsasSeq.addDBRef(dbref);
 +        // vamsasSeq.addDBRef(dbref);
 +        vamsasSeq.getDBRef().add(dbref);
        }
      }
      return vamsasSeq;
          MapListFrom mfrom = new MapListFrom();
          mfrom.setStart(range[0]);
          mfrom.setEnd(range[1]);
 -        mp.addMapListFrom(mfrom);
 +        // mp.addMapListFrom(mfrom);
 +        mp.getMapListFrom().add(mfrom);
        }
        r = mlst.getToRanges();
        for (int[] range : r)
          MapListTo mto = new MapListTo();
          mto.setStart(range[0]);
          mto.setEnd(range[1]);
 -        mp.addMapListTo(mto);
 +        // mp.addMapListTo(mto);
 +        mp.getMapListTo().add(mto);
        }
 -      mp.setMapFromUnit(mlst.getFromRatio());
 -      mp.setMapToUnit(mlst.getToRatio());
 +      mp.setMapFromUnit(BigInteger.valueOf(mlst.getFromRatio()));
 +      mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
        if (jmp.getTo() != null)
        {
 -        MappingChoice mpc = new MappingChoice();
 +        // MappingChoice mpc = new MappingChoice();
  
          // check/create ID for the sequence referenced by getTo()
  
          {
            jmpid = seqHash(ps = parentseq);
          }
 -        mpc.setDseqFor(jmpid);
 -        if (!seqRefIds.containsKey(mpc.getDseqFor()))
 +        // mpc.setDseqFor(jmpid);
 +        mp.setDseqFor(jmpid);
 +        if (!seqRefIds.containsKey(jmpid))
          {
            jalview.bin.Cache.log.debug("creatign new DseqFor ID");
 -          seqRefIds.put(mpc.getDseqFor(), ps);
 +          seqRefIds.put(jmpid, ps);
          }
          else
          {
            jalview.bin.Cache.log.debug("reusing DseqFor ID");
          }
  
 -        mp.setMappingChoice(mpc);
 +        // mp.setMappingChoice(mpc);
        }
      }
      return mp;
    }
  
    String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
 -          List<UserColourScheme> userColours, JalviewModelSequence jms)
 +          List<UserColourScheme> userColours, JalviewModel jm)
    {
      String id = null;
      jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
      {
        // actually create the scheme's entry in the XML model
        java.awt.Color[] colours = ucs.getColours();
 -      jalview.schemabinding.version2.UserColours uc = new jalview.schemabinding.version2.UserColours();
 -      jalview.schemabinding.version2.UserColourScheme jbucs = new jalview.schemabinding.version2.UserColourScheme();
 +      UserColours uc = new UserColours();
 +      // UserColourScheme jbucs = new UserColourScheme();
 +      JalviewUserColours jbucs = new JalviewUserColours();
  
        for (int i = 0; i < colours.length; i++)
        {
 -        jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
 +        Colour col = new Colour();
          col.setName(ResidueProperties.aa[i]);
          col.setRGB(jalview.util.Format.getHexString(colours[i]));
 -        jbucs.addColour(col);
 +        // jbucs.addColour(col);
 +        jbucs.getColour().add(col);
        }
        if (ucs.getLowerCaseColours() != null)
        {
          colours = ucs.getLowerCaseColours();
          for (int i = 0; i < colours.length; i++)
          {
 -          jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
 +          Colour col = new Colour();
            col.setName(ResidueProperties.aa[i].toLowerCase());
            col.setRGB(jalview.util.Format.getHexString(colours[i]));
 -          jbucs.addColour(col);
 +          // jbucs.addColour(col);
 +          jbucs.getColour().add(col);
          }
        }
  
        uc.setId(id);
        uc.setUserColourScheme(jbucs);
 -      jms.addUserColours(uc);
 +      // jm.addUserColours(uc);
 +      jm.getUserColours().add(uc);
      }
  
      return id;
    }
  
    jalview.schemes.UserColourScheme getUserColourScheme(
 -          JalviewModelSequence jms, String id)
 +          JalviewModel jm, String id)
    {
 -    UserColours[] uc = jms.getUserColours();
 +    List<UserColours> uc = jm.getUserColours();
      UserColours colours = null;
 -
 +/*
      for (int i = 0; i < uc.length; i++)
      {
        if (uc[i].getId().equals(id))
        {
          colours = uc[i];
 -
 +        break;
 +      }
 +    }
 +*/
 +    for (UserColours c : uc)
 +    {
 +      if (c.getId().equals(id))
 +      {
 +        colours = c;
          break;
        }
      }
      for (int i = 0; i < 24; i++)
      {
        newColours[i] = new java.awt.Color(Integer.parseInt(
 -              colours.getUserColourScheme().getColour(i).getRGB(), 16));
 +              // colours.getUserColourScheme().getColour(i).getRGB(), 16));
 +              colours.getUserColourScheme().getColour().get(i).getRGB(),
 +              16));
      }
  
      jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
              newColours);
  
 -    if (colours.getUserColourScheme().getColourCount() > 24)
 +    if (colours.getUserColourScheme().getColour().size()/*Count()*/ > 24)
      {
        newColours = new java.awt.Color[23];
        for (int i = 0; i < 23; i++)
        {
          newColours[i] = new java.awt.Color(Integer.parseInt(
 -                colours.getUserColourScheme().getColour(i + 24).getRGB(),
 +                colours.getUserColourScheme().getColour().get(i + 24)
 +                        .getRGB(),
                  16));
        }
        ucs.setLowerCaseColours(newColours);
     * flag to control whether the Jalview2XML_V1 parser should be deferred to if
     * exceptions are raised during project XML parsing
     */
 -  public boolean attemptversion1parse = true;
 +  public boolean attemptversion1parse = false;
  
    /**
     * Load a jalview project archive from a jar file
  
        jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
        af = loadJalviewAlign(jprovider);
 -      af.setMenusForViewport();
 -
 +      if (af != null)
 +      {
 +        af.setMenusForViewport();
 +      }
      } catch (MalformedURLException e)
      {
        errorMessage = "Invalid URL format for '" + file + "'";
          if (jarentry != null && jarentry.getName().endsWith(".xml"))
          {
            InputStreamReader in = new InputStreamReader(jin, UTF_8);
 -          JalviewModel object = new JalviewModel();
 +          // JalviewModel object = new JalviewModel();
  
 +          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();
 +
 +          /*
            Unmarshaller unmar = new Unmarshaller(object);
            unmar.setValidation(false);
            object = (JalviewModel) unmar.unmarshal(in);
 +          */
            if (true) // !skipViewport(object))
            {
              _af = loadFromObject(object, file, true, jprovider);
 -            if (_af != null && object.getJalviewModelSequence()
 -                    .getViewportCount() > 0)
 +            if (_af != null && object.getViewport().size() > 0)
 +            // getJalviewModelSequence().getViewportCount() > 0)
              {
                if (af == null)
                {
                  // store a reference to the first view
                  af = _af;
                }
 -              if (_af.viewport.isGatherViewsHere())
 +              if (_af.getViewport().isGatherViewsHere())
                {
                  // if this is a gathered view, keep its reference since
                  // after gathering views, only this frame will remain
                  af = _af;
 -                gatherToThisFrame.put(_af.viewport.getSequenceSetId(), _af);
 +                gatherToThisFrame.put(_af.getViewport().getSequenceSetId(),
 +                        _af);
                }
                // Save dataset to register mappings once all resolved
 -              importedDatasets.put(af.viewport.getAlignment().getDataset(),
 -                      af.viewport.getAlignment().getDataset());
 +              importedDatasets.put(
 +                      af.getViewport().getAlignment().getDataset(),
 +                      af.getViewport().getAlignment().getDataset());
              }
            }
            entryCount++;
        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)
        {
            addedToSplitFrames.add(af);
            dnaFrame.setMenusForViewport();
            af.setMenusForViewport();
 -          if (af.viewport.isGatherViewsHere())
 +          if (af.getViewport().isGatherViewsHere())
            {
              gatherTo.add(sf);
            }
        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");
       * And compute cDNA consensus (couldn't do earlier with consensus as
       * mappings were not yet present)
       */
 -    proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
 +    proteinFrame.getViewport().alignmentChanged(proteinFrame.alignPanel);
  
      return splitFrame;
    }
    /**
     * Load alignment frame from jalview XML DOM object
     * 
 -   * @param object
 +   * @param jalviewModel
     *          DOM
     * @param file
     *          filename source string
     *          data source provider
     * @return alignment frame created from view stored in DOM
     */
 -  AlignFrame loadFromObject(JalviewModel object, String file,
 +  AlignFrame loadFromObject(JalviewModel jalviewModel, String file,
            boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
    {
 -    SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
 -    Sequence[] vamsasSeq = vamsasSet.getSequence();
 +    SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet().get(0);
 +    List<Sequence> vamsasSeqs = vamsasSet.getSequence();
  
 -    JalviewModelSequence jms = object.getJalviewModelSequence();
 +    // JalviewModelSequence jms = object.getJalviewModelSequence();
  
 -    Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
 +    // Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
 +    // : null;
 +    Viewport view = (jalviewModel.getViewport().size() > 0)
 +            ? jalviewModel.getViewport().get(0)
              : 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;
  
      boolean multipleView = false;
      SequenceI referenceseqForView = null;
 -    JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
 +    // JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
 +    List<JSeq> jseqs = jalviewModel.getJSeq();
      int vi = 0; // counter in vamsasSeq array
 -    for (int i = 0; i < jseqs.length; i++)
 +    for (int i = 0; i < jseqs.size(); i++)
      {
 -      String seqId = jseqs[i].getId();
 +      JSeq jseq = jseqs.get(i);
 +      String seqId = jseq.getId();
  
        SequenceI tmpSeq = seqRefIds.get(seqId);
        if (tmpSeq != null)
          if (!incompleteSeqs.containsKey(seqId))
          {
            // may not need this check, but keep it for at least 2.9,1 release
 -          if (tmpSeq.getStart() != jseqs[i].getStart()
 -                  || tmpSeq.getEnd() != jseqs[i].getEnd())
 +          if (tmpSeq.getStart() != jseq.getStart()
 +                  || tmpSeq.getEnd() != jseq.getEnd())
            {
              System.err.println(
                      "Warning JAL-2154 regression: updating start/end for sequence "
 -                            + tmpSeq.toString() + " to " + jseqs[i]);
 +                            + tmpSeq.toString() + " to " + jseq);
            }
          }
          else
          {
            incompleteSeqs.remove(seqId);
          }
 -        if (vamsasSeq.length > vi && vamsasSeq[vi].getId().equals(seqId))
 +        if (vamsasSeqs.size() > vi
 +                && vamsasSeqs.get(vi).getId().equals(seqId))
          {
            // most likely we are reading a dataset XML document so
            // update from vamsasSeq section of XML for this sequence
 -          tmpSeq.setName(vamsasSeq[vi].getName());
 -          tmpSeq.setDescription(vamsasSeq[vi].getDescription());
 -          tmpSeq.setSequence(vamsasSeq[vi].getSequence());
 +          tmpSeq.setName(vamsasSeqs.get(vi).getName());
 +          tmpSeq.setDescription(vamsasSeqs.get(vi).getDescription());
 +          tmpSeq.setSequence(vamsasSeqs.get(vi).getSequence());
            vi++;
          }
          else
            // reading multiple views, so vamsasSeq set is a subset of JSeq
            multipleView = true;
          }
 -        tmpSeq.setStart(jseqs[i].getStart());
 -        tmpSeq.setEnd(jseqs[i].getEnd());
 +        tmpSeq.setStart(jseq.getStart());
 +        tmpSeq.setEnd(jseq.getEnd());
          tmpseqs.add(tmpSeq);
        }
        else
        {
 -        tmpSeq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
 -                vamsasSeq[vi].getSequence());
 -        tmpSeq.setDescription(vamsasSeq[vi].getDescription());
 -        tmpSeq.setStart(jseqs[i].getStart());
 -        tmpSeq.setEnd(jseqs[i].getEnd());
 +        Sequence vamsasSeq = vamsasSeqs.get(vi);
 +        tmpSeq = new jalview.datamodel.Sequence(vamsasSeq.getName(),
 +                vamsasSeq.getSequence());
 +        tmpSeq.setDescription(vamsasSeq.getDescription());
 +        tmpSeq.setStart(jseq.getStart());
 +        tmpSeq.setEnd(jseq.getEnd());
          tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
 -        seqRefIds.put(vamsasSeq[vi].getId(), tmpSeq);
 +        seqRefIds.put(vamsasSeq.getId(), tmpSeq);
          tmpseqs.add(tmpSeq);
          vi++;
        }
  
 -      if (jseqs[i].hasViewreference() && jseqs[i].getViewreference())
 +      if (safeBoolean(jseq.isViewreference()))
        {
          referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
        }
  
 -      if (jseqs[i].getHidden())
 +      if (jseq.isHidden() != null && jseq.isHidden().booleanValue())
        {
          if (hiddenSeqs == null)
          {
      }
      else
      {
 -      boolean isdsal = object.getJalviewModelSequence()
 -              .getViewportCount() == 0;
 +      boolean isdsal = jalviewModel.getViewport().isEmpty();
        if (isdsal)
        {
          // we are importing a dataset record, so
  
        // 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)
        al.setSeqrep(referenceseqForView);
      }
      // / Add the alignment properties
 -    for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
 +    for (int i = 0; i < vamsasSet.getSequenceSetProperties().size(); i++)
      {
 -      SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
 +      SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties()
 +              .get(i);
        al.setProperty(ssp.getKey(), ssp.getValue());
      }
  
        // now, for 2.10 projects, this is also done if the xml doc includes
        // dataset sequences not actually present in any particular view.
        //
 -      for (int i = 0; i < vamsasSeq.length; i++)
 +      for (int i = 0; i < vamsasSeqs.size(); i++)
        {
 -        if (jseqs[i].getFeaturesCount() > 0)
 +        JSeq jseq = jseqs.get(i);
 +        if (jseq.getFeatures().size() > 0)
          {
 -          Features[] features = jseqs[i].getFeatures();
 -          for (int f = 0; f < features.length; f++)
 +          List<Feature> features = jseq.getFeatures();
 +          for (int f = 0; f < features.size(); f++)
            {
 -            SequenceFeature sf = new SequenceFeature(features[f].getType(),
 -                    features[f].getDescription(), features[f].getBegin(),
 -                    features[f].getEnd(), features[f].getScore(),
 -                    features[f].getFeatureGroup());
 -            sf.setStatus(features[f].getStatus());
 +            Feature feat = features.get(f);
 +            SequenceFeature sf = new SequenceFeature(feat.getType(),
 +                    feat.getDescription(), feat.getBegin(), feat.getEnd(),
 +                    safeFloat(feat.getScore()), feat.getFeatureGroup());
 +            sf.setStatus(feat.getStatus());
  
              /*
               * load any feature attributes - include map-valued attributes
               */
              Map<String, Map<String, String>> mapAttributes = new HashMap<>();
 -            for (int od = 0; od < features[f].getOtherDataCount(); od++)
 +            for (int od = 0; od < feat.getOtherData().size(); od++)
              {
 -              OtherData keyValue = features[f].getOtherData(od);
 +              OtherData keyValue = feat.getOtherData().get(od);
                String attributeName = keyValue.getKey();
                String attributeValue = keyValue.getValue();
                if (attributeName.startsWith("LINK"))
              al.getSequenceAt(i).addSequenceFeature(sf);
            }
          }
 -        if (vamsasSeq[i].getDBRefCount() > 0)
 +        if (vamsasSeqs.get(i).getDBRef().size() > 0)
          {
            // adds dbrefs to datasequence's set (since Jalview 2.10)
            addDBRefs(
                    al.getSequenceAt(i).getDatasetSequence() == null
                            ? al.getSequenceAt(i)
                            : al.getSequenceAt(i).getDatasetSequence(),
 -                  vamsasSeq[i]);
 +                  vamsasSeqs.get(i));
          }
 -        if (jseqs[i].getPdbidsCount() > 0)
 +        if (jseq.getPdbids().size() > 0)
          {
 -          Pdbids[] ids = jseqs[i].getPdbids();
 -          for (int p = 0; p < ids.length; p++)
 +          List<Pdbids> ids = jseq.getPdbids();
 +          for (int p = 0; p < ids.size(); p++)
            {
 +            Pdbids pdbid = ids.get(p);
              jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
 -            entry.setId(ids[p].getId());
 -            if (ids[p].getType() != null)
 +            entry.setId(pdbid.getId());
 +            if (pdbid.getType() != null)
              {
 -              if (PDBEntry.Type.getType(ids[p].getType()) != null)
 +              if (PDBEntry.Type.getType(pdbid.getType()) != null)
                {
 -                entry.setType(PDBEntry.Type.getType(ids[p].getType()));
 +                entry.setType(PDBEntry.Type.getType(pdbid.getType()));
                }
                else
                {
                }
              }
              // jprovider is null when executing 'New View'
 -            if (ids[p].getFile() != null && jprovider != null)
 +            if (pdbid.getFile() != null && jprovider != null)
              {
 -              if (!pdbloaded.containsKey(ids[p].getFile()))
 +              if (!pdbloaded.containsKey(pdbid.getFile()))
                {
 -                entry.setFile(loadPDBFile(jprovider, ids[p].getId(),
 -                        ids[p].getFile()));
 +                entry.setFile(loadPDBFile(jprovider, pdbid.getId(),
 +                        pdbid.getFile()));
                }
                else
                {
 -                entry.setFile(pdbloaded.get(ids[p].getId()).toString());
 +                entry.setFile(pdbloaded.get(pdbid.getId()).toString());
                }
              }
 -            if (ids[p].getPdbentryItem() != null)
 +            /*
 +            if (pdbid.getPdbentryItem() != null)
              {
 -              for (PdbentryItem item : ids[p].getPdbentryItem())
 +              for (PdbentryItem item : pdbid.getPdbentryItem())
                {
                  for (Property pr : item.getProperty())
                  {
                  }
                }
              }
 +            */
 +            for (Property prop : pdbid.getProperty())
 +            {
 +              entry.setProperty(prop.getName(), prop.getValue());
 +            }
              StructureSelectionManager
                      .getStructureSelectionManager(Desktop.instance)
                      .registerPDBEntry(entry);
      // ///////////////////////////////
      // LOAD SEQUENCE MAPPINGS
  
 -    if (vamsasSet.getAlcodonFrameCount() > 0)
 +    if (vamsasSet.getAlcodonFrame().size() > 0)
      {
        // TODO Potentially this should only be done once for all views of an
        // alignment
 -      AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
 -      for (int i = 0; i < alc.length; i++)
 +      List<AlcodonFrame> alc = vamsasSet.getAlcodonFrame();
 +      for (int i = 0; i < alc.size(); i++)
        {
          AlignedCodonFrame cf = new AlignedCodonFrame();
 -        if (alc[i].getAlcodMapCount() > 0)
 +        if (alc.get(i).getAlcodMap().size() > 0)
          {
 -          AlcodMap[] maps = alc[i].getAlcodMap();
 -          for (int m = 0; m < maps.length; m++)
 +          List<AlcodMap> maps = alc.get(i).getAlcodMap();
 +          for (int m = 0; m < maps.size(); m++)
            {
 -            SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
 +            AlcodMap map = maps.get(m);
 +            SequenceI dnaseq = seqRefIds.get(map.getDnasq());
              // Load Mapping
              jalview.datamodel.Mapping mapping = null;
              // attach to dna sequence reference.
 -            if (maps[m].getMapping() != null)
 +            if (map.getMapping() != null)
              {
 -              mapping = addMapping(maps[m].getMapping());
 +              mapping = addMapping(map.getMapping());
                if (dnaseq != null && mapping.getTo() != null)
                {
                  cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
                {
                  // defer to later
                  frefedSequence.add(
 -                        newAlcodMapRef(maps[m].getDnasq(), cf, mapping));
 +                        newAlcodMapRef(map.getDnasq(), cf, mapping));
                }
              }
            }
       */
      Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
  
 -    if (vamsasSet.getAnnotationCount() > 0)
 +    if (vamsasSet.getAnnotation().size()/*Count()*/ > 0)
      {
 -      Annotation[] an = vamsasSet.getAnnotation();
 +      List<Annotation> an = vamsasSet.getAnnotation();
  
 -      for (int i = 0; i < an.length; i++)
 +      for (int i = 0; i < an.size(); i++)
        {
 -        Annotation annotation = an[i];
 +        Annotation annotation = an.get(i);
  
          /**
           * test if annotation is automatically calculated for this view only
          {
            // Kludge for pre 2.5 projects which lacked the autocalculated flag
            autoForView = true;
 -          if (!annotation.hasAutoCalculated())
 -          {
 -            annotation.setAutoCalculated(true);
 -          }
 +          // JAXB has no has() test; schema defaults value to false
 +          // if (!annotation.hasAutoCalculated())
 +          // {
 +          // 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.hasVisible())
 +          if (annotation.isVisible() != null)
            {
 -            jda.visible = annotation.getVisible();
 +            jda.visible = annotation.isVisible();
            }
  
            al.addAnnotation(jda);
            continue;
          }
          // Construct new annotation from model.
 -        AnnotationElement[] ae = annotation.getAnnotationElement();
 +        List<AnnotationElement> ae = annotation.getAnnotationElement();
          jalview.datamodel.Annotation[] anot = null;
          java.awt.Color firstColour = null;
          int anpos;
 -        if (!annotation.getScoreOnly())
 +        if (!annotation.isScoreOnly())
          {
            anot = new jalview.datamodel.Annotation[al.getWidth()];
 -          for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
 +          for (int aa = 0; aa < ae.size() && aa < anot.length; aa++)
            {
 -            anpos = ae[aa].getPosition();
 +            AnnotationElement annElement = ae.get(aa);
 +            anpos = annElement.getPosition();
  
              if (anpos >= anot.length)
              {
                continue;
              }
  
 +            float value = safeFloat(annElement.getValue());
              anot[anpos] = new jalview.datamodel.Annotation(
 -
 -                    ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
 -                    (ae[aa].getSecondaryStructure() == null
 -                            || ae[aa].getSecondaryStructure().length() == 0)
 -                                    ? ' '
 -                                    : ae[aa].getSecondaryStructure()
 -                                            .charAt(0),
 -                    ae[aa].getValue()
 -
 -            );
 -            // JBPNote: Consider verifying dataflow for IO of secondary
 -            // structure annotation read from Stockholm files
 -            // this was added to try to ensure that
 -            // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
 -            // {
 -            // anot[ae[aa].getPosition()].displayCharacter = "";
 -            // }
 -            anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
 +                    annElement.getDisplayCharacter(),
 +                    annElement.getDescription(),
 +                    (annElement.getSecondaryStructure() == null
 +                            || annElement.getSecondaryStructure()
 +                                    .length() == 0)
 +                                            ? ' '
 +                                            : annElement
 +                                                    .getSecondaryStructure()
 +                                                    .charAt(0),
 +                    value);
 +            anot[anpos].colour = new Color(safeInt(annElement.getColour()));
              if (firstColour == null)
              {
                firstColour = anot[anpos].colour;
          }
          jalview.datamodel.AlignmentAnnotation jaa = null;
  
 -        if (annotation.getGraph())
 +        if (annotation.isGraph())
          {
            float llim = 0, hlim = 0;
            // if (autoForView || an[i].isAutoCalculated()) {
            // }
            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())
            {
          }
          else
          {
 -          jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
 -                  an[i].getDescription(), anot);
 +          jaa = new jalview.datamodel.AlignmentAnnotation(
 +                  annotation.getLabel(), annotation.getDescription(), anot);
            jaa._linecolour = firstColour;
          }
          // register new annotation
 -        if (an[i].getId() != null)
 +        if (annotation.getId() != null)
          {
 -          annotationIds.put(an[i].getId(), jaa);
 -          jaa.annotationId = an[i].getId();
 +          annotationIds.put(annotation.getId(), jaa);
 +          jaa.annotationId = annotation.getId();
          }
          // recover sequence association
 -        String sequenceRef = an[i].getSequenceRef();
 +        String sequenceRef = annotation.getSequenceRef();
          if (sequenceRef != null)
          {
            // from 2.9 sequenceRef is to sequence id (JAL-1781)
            }
          }
          // and make a note of any group association
 -        if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
 +        if (annotation.getGroupRef() != null
 +                && annotation.getGroupRef().length() > 0)
          {
            List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
 -                  .get(an[i].getGroupRef());
 +                  .get(annotation.getGroupRef());
            if (aal == null)
            {
              aal = new ArrayList<>();
 -            groupAnnotRefs.put(an[i].getGroupRef(), aal);
 +            groupAnnotRefs.put(annotation.getGroupRef(), aal);
            }
            aal.add(jaa);
          }
  
 -        if (an[i].hasScore())
 +        if (annotation.getScore() != null)
          {
 -          jaa.setScore(an[i].getScore());
 +          jaa.setScore(annotation.getScore().doubleValue());
          }
 -        if (an[i].hasVisible())
 +        if (annotation.isVisible() != null)
          {
 -          jaa.visible = an[i].getVisible();
 +          jaa.visible = annotation.isVisible().booleanValue();
          }
  
 -        if (an[i].hasCentreColLabels())
 +        if (annotation.isCentreColLabels() != null)
          {
 -          jaa.centreColLabels = an[i].getCentreColLabels();
 +          jaa.centreColLabels = annotation.isCentreColLabels()
 +                  .booleanValue();
          }
  
 -        if (an[i].hasScaleColLabels())
 +        if (annotation.isScaleColLabels() != null)
          {
 -          jaa.scaleColLabel = an[i].getScaleColLabels();
 +          jaa.scaleColLabel = annotation.isScaleColLabels().booleanValue();
          }
 -        if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
 +        if (annotation.isAutoCalculated())
          {
            // newer files have an 'autoCalculated' flag and store calculation
            // state in viewport properties
            jaa.autoCalculated = true; // means annotation will be marked for
            // update at end of load.
          }
 -        if (an[i].hasGraphHeight())
 -        {
 -          jaa.graphHeight = an[i].getGraphHeight();
 -        }
 -        if (an[i].hasBelowAlignment())
 +        if (annotation.getGraphHeight() != null)
          {
 -          jaa.belowAlignment = an[i].isBelowAlignment();
 +          jaa.graphHeight = annotation.getGraphHeight().intValue();
          }
 -        jaa.setCalcId(an[i].getCalcId());
 -        if (an[i].getPropertyCount() > 0)
 +        jaa.belowAlignment = annotation.isBelowAlignment();
 +        jaa.setCalcId(annotation.getCalcId());
 +        if (annotation.getProperty().size() > 0)
          {
 -          for (jalview.schemabinding.version2.Property prop : an[i]
 +          for (Annotation.Property prop : annotation
                    .getProperty())
            {
              jaa.setProperty(prop.getName(), prop.getValue());
      // ///////////////////////
      // LOAD GROUPS
      // Create alignment markup and styles for this view
 -    if (jms.getJGroupCount() > 0)
 +    if (jalviewModel.getJGroup().size() > 0)
      {
 -      JGroup[] groups = jms.getJGroup();
 +      List<JGroup> groups = jalviewModel.getJGroup();
        boolean addAnnotSchemeGroup = false;
 -      for (int i = 0; i < groups.length; i++)
 +      for (int i = 0; i < groups.size(); i++)
        {
 -        JGroup jGroup = groups[i];
 +        JGroup jGroup = groups.get(i);
          ColourSchemeI cs = null;
          if (jGroup.getColour() != null)
          {
            if (jGroup.getColour().startsWith("ucs"))
            {
 -            cs = getUserColourScheme(jms, jGroup.getColour());
 +            cs = getUserColourScheme(jalviewModel, jGroup.getColour());
            }
            else if (jGroup.getColour().equals("AnnotationColourGradient")
                    && jGroup.getAnnotationColours() != null)
                      jGroup.getColour());
            }
          }
 -        int pidThreshold = jGroup.getPidThreshold();
 +        int pidThreshold = safeInt(jGroup.getPidThreshold());
  
          Vector<SequenceI> seqs = new Vector<>();
  
 -        for (int s = 0; s < jGroup.getSeqCount(); s++)
 +        for (int s = 0; s < jGroup.getSeq().size(); s++)
          {
 -          String seqId = jGroup.getSeq(s) + "";
 +          String seqId = jGroup.getSeq().get(s);
            SequenceI ts = seqRefIds.get(seqId);
  
            if (ts != null)
          }
  
          SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
 -                jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
 -                jGroup.getColourText(), 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());
 -        sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
 -
 -        sg.textColour = new java.awt.Color(jGroup.getTextCol1());
 -        sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
 -        sg.setShowNonconserved(
 -                jGroup.hasShowUnconserved() ? jGroup.isShowUnconserved()
 -                        : false);
 -        sg.thresholdTextColour = jGroup.getTextColThreshold();
 -        if (jGroup.hasShowConsensusHistogram())
 -        {
 +                .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
            sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
 -        }
 -        ;
 -        if (jGroup.hasShowSequenceLogo())
 -        {
            sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
 -        }
 -        if (jGroup.hasNormaliseSequenceLogo())
 -        {
            sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
 -        }
 -        if (jGroup.hasIgnoreGapsinConsensus())
 -        {
 -          sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
 -        }
 -        if (jGroup.getConsThreshold() != 0)
 +        sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus());
 +        if (jGroup.getConsThreshold() != null
 +                && jGroup.getConsThreshold().intValue() != 0)
          {
            Conservation c = new Conservation("All", sg.getSequences(null), 0,
                    sg.getWidth() - 1);
          {
            // reconstruct the annotation colourscheme
            sg.setColourScheme(constructAnnotationColour(
 -                  jGroup.getAnnotationColours(), null, al, jms, false));
 +                  jGroup.getAnnotationColours(), null, al, jalviewModel, false));
          }
        }
      }
      // ///////////////////////////////
      // 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.
       * Jalview 2.8.1 behaviour)
       */
      boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
 -            object.getVersion());
 +            jalviewModel.getVersion());
  
      AlignmentPanel ap = null;
      boolean isnewview = true;
  
      if (isnewview)
      {
 -      af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
 +      af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
                uniqueSeqSetId, viewId, autoAlan);
 -      av = af.viewport;
 +      av = af.getViewport();
        ap = af.alignPanel;
      }
  
       */
      if (loadTreesAndStructures)
      {
 -      loadTrees(jms, view, af, av, ap);
 +      loadTrees(jalviewModel, view, af, av, ap);
 +      loadPCAViewers(jalviewModel, ap);
        loadPDBStructures(jprovider, jseqs, af, ap);
        loadRnaViewers(jprovider, jseqs, ap);
      }
     * @param ap
     */
    private void loadRnaViewers(jarInputStreamProvider jprovider,
 -          JSeq[] jseqs, AlignmentPanel ap)
 +          List<JSeq> jseqs, AlignmentPanel ap)
    {
      /*
       * scan the sequences for references to viewers; create each one the first
       */
      for (JSeq jseq : jseqs)
      {
 -      for (int i = 0; i < jseq.getRnaViewerCount(); i++)
 +      for (int i = 0; i < jseq.getRnaViewer().size(); i++)
        {
 -        RnaViewer viewer = jseq.getRnaViewer(i);
 +        RnaViewer viewer = jseq.getRnaViewer().get(i);
          AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
                  ap);
  
 -        for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
 +        for (int j = 0; j < viewer.getSecondaryStructure().size(); j++)
          {
 -          SecondaryStructure ss = viewer.getSecondaryStructure(j);
 +          SecondaryStructure ss = viewer.getSecondaryStructure().get(j);
            SequenceI seq = seqRefIds.get(jseq.getId());
            AlignmentAnnotation ann = this.annotationIds
                    .get(ss.getAnnotationId());
             * 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,
            RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
            appVarna.addModelSession(rna, rnaTitle, tempStateFile);
          }
 -        appVarna.setInitialSelection(viewer.getSelectedRna());
 +        appVarna.setInitialSelection(safeInt(viewer.getSelectedRna()));
        }
      }
    }
       * 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;
    /**
     * Load any saved trees
     * 
 -   * @param jms
 +   * @param jm
     * @param view
     * @param af
     * @param av
     * @param ap
     */
 -  protected void loadTrees(JalviewModelSequence jms, Viewport view,
 +  protected void loadTrees(JalviewModel jm, Viewport view,
            AlignFrame af, AlignViewport av, AlignmentPanel ap)
    {
      // TODO result of automated refactoring - are all these parameters needed?
      try
      {
 -      for (int t = 0; t < jms.getTreeCount(); t++)
 +      for (int t = 0; t < jm.getTree().size(); t++)
        {
  
 -        Tree tree = jms.getTree(t);
 +        Tree tree = jm.getTree().get(t);
  
          TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
          if (tp == null)
          {
 -          tp = af.showNewickTree(
 -                  new jalview.io.NewickFile(tree.getNewick()),
 -                  tree.getTitle(), tree.getWidth(), tree.getHeight(),
 -                  tree.getXpos(), tree.getYpos());
 +          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 ?
            // TODO: should check if tp has been manipulated by user - if so its
            // settings shouldn't be modified
            tp.setTitle(tree.getTitle());
 -          tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(),
 -                  tree.getWidth(), tree.getHeight()));
 -          tp.av = av; // af.viewport; // TODO: verify 'associate with all
 -          // views'
 -          // works still
 -          tp.treeCanvas.av = av; // af.viewport;
 -          tp.treeCanvas.ap = ap; // af.alignPanel;
 +          tp.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)
            continue;
          }
  
 -        tp.fitToWindow.setState(tree.getFitToWindow());
 +        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.getMarkUnlinked());
 -        tp.showBootstrap(tree.getShowBootstrap());
 -        tp.showDistances(tree.getShowDistances());
 +        tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
 +        tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
 +        tp.showDistances(safeBoolean(tree.isShowDistances()));
  
 -        tp.treeCanvas.threshold = tree.getThreshold();
 +        tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
  
 -        if (tree.getCurrentTree())
 +        if (safeBoolean(tree.isCurrentTree()))
          {
 -          af.viewport.setCurrentTree(tp.getTree());
 +          af.getViewport().setCurrentTree(tp.getTree());
          }
        }
  
     * @param ap
     */
    protected void loadPDBStructures(jarInputStreamProvider jprovider,
 -          JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
 +          List<JSeq> jseqs, AlignFrame af, AlignmentPanel ap)
    {
      /*
       * Run through all PDB ids on the alignment, and collect mappings between
       */
      Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
  
 -    for (int i = 0; i < jseqs.length; i++)
 +    for (int i = 0; i < jseqs.size(); i++)
      {
 -      if (jseqs[i].getPdbidsCount() > 0)
 +      JSeq jseq = jseqs.get(i);
 +      if (jseq.getPdbids().size() > 0)
        {
 -        Pdbids[] ids = jseqs[i].getPdbids();
 -        for (int p = 0; p < ids.length; p++)
 +        List<Pdbids> ids = jseq.getPdbids();
 +        for (int p = 0; p < ids.size(); p++)
          {
 -          final int structureStateCount = ids[p].getStructureStateCount();
 +          Pdbids pdbid = ids.get(p);
 +          final int structureStateCount = pdbid.getStructureState().size();
            for (int s = 0; s < structureStateCount; s++)
            {
              // check to see if we haven't already created this structure view
 -            final StructureState structureState = ids[p]
 -                    .getStructureState(s);
 +            final StructureState structureState = pdbid
 +                    .getStructureState().get(s);
              String sviewid = (structureState.getViewId() == null) ? null
                      : structureState.getViewId() + uniqueSetSuffix;
              jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
 -            // Originally : ids[p].getFile()
 +            // Originally : pdbid.getFile()
              // : TODO: verify external PDB file recovery still works in normal
              // jalview project load
 -            jpdb.setFile(loadPDBFile(jprovider, ids[p].getId(),
 -                    ids[p].getFile()));
 -            jpdb.setId(ids[p].getId());
 +            jpdb.setFile(
 +                    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);
              // TODO: NOW: check that this recovers the PDB file correctly.
 -            String pdbFile = loadPDBFile(jprovider, ids[p].getId(),
 -                    ids[p].getFile());
 +            String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
 +                    pdbid.getFile());
              jalview.datamodel.SequenceI seq = seqRefIds
 -                    .get(jseqs[i].getId() + "");
 +                    .get(jseq.getId() + "");
              if (sviewid == null)
              {
                sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
              // linkAlignPanel,superposeWithAlignpanel}} from hash
              StructureViewerModel jmoldat = structureViewers.get(sviewid);
              jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
 -                    | (structureState.hasAlignwithAlignPanel()
 -                            ? structureState.getAlignwithAlignPanel()
 -                            : false));
 +                    || structureState.isAlignwithAlignPanel());
  
              /*
               * Default colour by linked panel to false if not specified (e.g.
               * for pre-2.7 projects)
               */
              boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
 -            colourWithAlignPanel |= (structureState
 -                    .hasColourwithAlignPanel()
 -                            ? structureState.getColourwithAlignPanel()
 -                            : false);
 +            colourWithAlignPanel |= structureState.isColourwithAlignPanel();
              jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
  
              /*
               * pre-2.7 projects)
               */
              boolean colourByViewer = jmoldat.isColourByViewer();
 -            colourByViewer &= structureState.hasColourByJmol()
 -                    ? structureState.getColourByJmol()
 -                    : true;
 +            colourByViewer &= structureState.isColourByJmol();
              jmoldat.setColourByViewer(colourByViewer);
  
              if (jmoldat.getStateData().length() < structureState
 -                    .getContent().length())
 +                    .getValue()/*Content()*/.length())
              {
 -              {
 -                jmoldat.setStateData(structureState.getContent());
 -              }
 +              jmoldat.setStateData(structureState.getValue());// Content());
              }
 -            if (ids[p].getFile() != null)
 +            if (pdbid.getFile() != null)
              {
 -              File mapkey = new File(ids[p].getFile());
 +              File mapkey = new File(pdbid.getFile());
                StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
                if (seqstrmaps == null)
                {
                  jmoldat.getFileData().put(mapkey,
                          seqstrmaps = jmoldat.new StructureData(pdbFile,
 -                                ids[p].getId()));
 +                                pdbid.getId()));
                }
                if (!seqstrmaps.getSeqList().contains(seq))
                {
      }
    }
  
 -  AlignFrame loadViewport(String file, JSeq[] JSEQ,
 +  AlignFrame loadViewport(String file, List<JSeq> JSEQ,
            List<SequenceI> hiddenSeqs, AlignmentI al,
 -          JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
 +          JalviewModel jm, Viewport view, String uniqueSeqSetId,
            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);
  
 -    for (int i = 0; i < JSEQ.length; i++)
 +    final AlignViewport viewport = af.getViewport();
 +    for (int i = 0; i < JSEQ.size(); i++)
      {
 -      af.viewport.setSequenceColour(
 -              af.viewport.getAlignment().getSequenceAt(i),
 -              new java.awt.Color(JSEQ[i].getColour()));
 +      int colour = safeInt(JSEQ.get(i).getColour());
 +      viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
 +              new Color(colour));
      }
  
      if (al.hasSeqrep())
      {
 -      af.getViewport().setColourByReferenceSeq(true);
 -      af.getViewport().setDisplayReferenceSeq(true);
 +      viewport.setColourByReferenceSeq(true);
 +      viewport.setDisplayReferenceSeq(true);
      }
  
 -    af.viewport.setGatherViewsHere(view.getGatheredViews());
 +    viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
  
      if (view.getSequenceSetId() != null)
      {
        AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
  
 -      af.viewport.setSequenceSetId(uniqueSeqSetId);
 +      viewport.setSequenceSetId(uniqueSeqSetId);
        if (av != null)
        {
          // propagate shared settings to this new view
 -        af.viewport.setHistoryList(av.getHistoryList());
 -        af.viewport.setRedoList(av.getRedoList());
 +        viewport.setHistoryList(av.getHistoryList());
 +        viewport.setRedoList(av.getRedoList());
        }
        else
        {
 -        viewportsAdded.put(uniqueSeqSetId, af.viewport);
 +        viewportsAdded.put(uniqueSeqSetId, viewport);
        }
        // TODO: check if this method can be called repeatedly without
        // side-effects if alignpanel already registered.
      // apply Hidden regions to view.
      if (hiddenSeqs != null)
      {
 -      for (int s = 0; s < JSEQ.length; s++)
 +      for (int s = 0; s < JSEQ.size(); s++)
        {
          SequenceGroup hidden = new SequenceGroup();
          boolean isRepresentative = false;
 -        for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
 +        for (int r = 0; r < JSEQ.get(s).getHiddenSequences().size(); r++)
          {
            isRepresentative = true;
            SequenceI sequenceToHide = al
 -                  .getSequenceAt(JSEQ[s].getHiddenSequences(r));
 +                  .getSequenceAt(JSEQ.get(s).getHiddenSequences().get(r));
            hidden.addSequence(sequenceToHide, false);
            // remove from hiddenSeqs list so we don't try to hide it twice
            hiddenSeqs.remove(sequenceToHide);
          {
            SequenceI representativeSequence = al.getSequenceAt(s);
            hidden.addSequence(representativeSequence, false);
 -          af.viewport.hideRepSequences(representativeSequence, hidden);
 +          viewport.hideRepSequences(representativeSequence, hidden);
          }
        }
  
        SequenceI[] hseqs = hiddenSeqs
                .toArray(new SequenceI[hiddenSeqs.size()]);
 -      af.viewport.hideSequence(hseqs);
 +      viewport.hideSequence(hseqs);
  
      }
      // recover view properties and display parameters
  
 -    af.viewport.setShowAnnotation(view.getShowAnnotation());
 -    af.viewport.setAbovePIDThreshold(view.getPidSelected());
 -    af.viewport.setThreshold(view.getPidThreshold());
 -
 -    af.viewport.setColourText(view.getShowColourText());
 -
 -    af.viewport.setConservationSelected(view.getConservationSelected());
 -    af.viewport.setIncrement(view.getConsThreshold());
 -    af.viewport.setShowJVSuffix(view.getShowFullId());
 -    af.viewport.setRightAlignIds(view.getRightAlignIds());
 -    af.viewport.setFont(new java.awt.Font(view.getFontName(),
 -            view.getFontStyle(), view.getFontSize()), true);
 -    ViewStyleI vs = af.viewport.getViewStyle();
 +    viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
 +    viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
 +    final int pidThreshold = safeInt(view.getPidThreshold());
 +    viewport.setThreshold(pidThreshold);
 +
 +    viewport.setColourText(safeBoolean(view.isShowColourText()));
 +
 +    viewport
 +            .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());
 -    af.viewport.setViewStyle(vs);
 +    viewport.setViewStyle(vs);
      // TODO: allow custom charWidth/Heights to be restored by updating them
      // after setting font - which means set above to false
 -    af.viewport.setRenderGaps(view.getRenderGaps());
 -    af.viewport.setWrapAlignment(view.getWrapAlignment());
 -    af.viewport.setShowAnnotation(view.getShowAnnotation());
 +    viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
 +    viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
 +    viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
  
 -    af.viewport.setShowBoxes(view.getShowBoxes());
 +    viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
  
 -    af.viewport.setShowText(view.getShowText());
 +    viewport.setShowText(safeBoolean(view.isShowText()));
  
 -    af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
 -    af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
 -    af.viewport.setThresholdTextColour(view.getTextColThreshold());
 -    af.viewport.setShowUnconserved(
 -            view.hasShowUnconserved() ? view.isShowUnconserved() : false);
 -    af.viewport.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)
      {
 -      af.viewport.viewName = view.getViewName();
 +      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;
      {
        if (view.getBgColour().startsWith("ucs"))
        {
 -        cs = getUserColourScheme(jms, view.getBgColour());
 +        cs = getUserColourScheme(jm, view.getBgColour());
        }
        else if (view.getBgColour().startsWith("Annotation"))
        {
 -        AnnotationColours viewAnnColour = view.getAnnotationColours();
 -        cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
 +        AnnotationColourScheme viewAnnColour = view.getAnnotationColours();
 +        cs = constructAnnotationColour(viewAnnColour, af, al, jm, true);
  
          // annpos
  
        }
      }
  
 -    af.viewport.setGlobalColourScheme(cs);
 -    af.viewport.getResidueShading().setThreshold(view.getPidThreshold(),
 -            view.getIgnoreGapsinConsensus());
 -    af.viewport.getResidueShading()
 -            .setConsensus(af.viewport.getSequenceConsensusHash());
 -    af.viewport.setColourAppliesToAllGroups(false);
 +    viewport.setGlobalColourScheme(cs);
 +    viewport.getResidueShading().setThreshold(pidThreshold,
 +            view.isIgnoreGapsinConsensus());
 +    viewport.getResidueShading()
 +            .setConsensus(viewport.getSequenceConsensusHash());
 +    viewport.setColourAppliesToAllGroups(false);
  
 -    if (view.getConservationSelected() && cs != null)
 +    if (safeBoolean(view.isConservationSelected()) && cs != null)
      {
 -      af.viewport.getResidueShading()
 -              .setConservationInc(view.getConsThreshold());
 +      viewport.getResidueShading()
 +              .setConservationInc(safeInt(view.getConsThreshold()));
      }
  
      af.changeColour(cs);
  
 -    af.viewport.setColourAppliesToAllGroups(true);
 +    viewport.setColourAppliesToAllGroups(true);
  
 -    af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
 +    viewport
 +            .setShowSequenceFeatures(
 +                    safeBoolean(view.isShowSequenceFeatures()));
  
 -    if (view.hasCentreColumnLabels())
 -    {
 -      af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
 -    }
 -    if (view.hasIgnoreGapsinConsensus())
 -    {
 -      af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
 -              null);
 -    }
 -    if (view.hasFollowHighlight())
 -    {
 -      af.viewport.setFollowHighlight(view.getFollowHighlight());
 -    }
 -    if (view.hasFollowSelection())
 -    {
 -      af.viewport.followSelection = view.getFollowSelection();
 -    }
 -    if (view.hasShowConsensusHistogram())
 -    {
 -      af.viewport
 -              .setShowConsensusHistogram(view.getShowConsensusHistogram());
 -    }
 -    else
 -    {
 -      af.viewport.setShowConsensusHistogram(true);
 -    }
 -    if (view.hasShowSequenceLogo())
 -    {
 -      af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
 -    }
 -    else
 -    {
 -      af.viewport.setShowSequenceLogo(false);
 -    }
 -    if (view.hasNormaliseSequenceLogo())
 -    {
 -      af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
 -    }
 -    if (view.hasShowDbRefTooltip())
 -    {
 -      af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
 -    }
 -    if (view.hasShowNPfeatureTooltip())
 -    {
 -      af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
 -    }
 -    if (view.hasShowGroupConsensus())
 -    {
 -      af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
 -    }
 -    else
 -    {
 -      af.viewport.setShowGroupConsensus(false);
 -    }
 -    if (view.hasShowGroupConservation())
 -    {
 -      af.viewport.setShowGroupConservation(view.getShowGroupConservation());
 -    }
 -    else
 -    {
 -      af.viewport.setShowGroupConservation(false);
 -    }
 +    viewport.setCentreColumnLabels(view.isCentreColumnLabels());
 +    viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
 +    viewport.setFollowHighlight(view.isFollowHighlight());
 +    viewport.followSelection = view.isFollowSelection();
 +    viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
 +    viewport.setShowSequenceLogo(view.isShowSequenceLogo());
 +    viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
 +    viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
 +    viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
 +    viewport.setShowGroupConsensus(view.isShowGroupConsensus());
 +    viewport.setShowGroupConservation(view.isShowGroupConservation());
  
      // recover feature settings
 -    if (jms.getFeatureSettings() != null)
 +    if (jm.getFeatureSettings() != null)
      {
        FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
                .getFeatureRenderer();
        FeaturesDisplayed fdi;
 -      af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
 -      String[] renderOrder = new String[jms.getFeatureSettings()
 -              .getSettingCount()];
 +      viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
 +      String[] renderOrder = new String[jm.getFeatureSettings()
 +              .getSetting().size()];
        Map<String, FeatureColourI> featureColours = new Hashtable<>();
        Map<String, Float> featureOrder = new Hashtable<>();
  
 -      for (int fs = 0; fs < jms.getFeatureSettings()
 -              .getSettingCount(); fs++)
 +      for (int fs = 0; fs < jm.getFeatureSettings()
 +              .getSetting().size(); fs++)
        {
 -        Setting setting = jms.getFeatureSettings().getSetting(fs);
 +        Setting setting = jm.getFeatureSettings().getSetting().get(fs);
          String featureType = setting.getType();
  
          /*
           * restore feature filters (if any)
           */
 -        MatcherSet filters = setting.getMatcherSet();
 +        jalview.xml.binding.jalview.FeatureMatcherSet filters = setting
 +                .getMatcherSet();
          if (filters != null)
          {
            FeatureMatcherSetI filter = Jalview2XML
 -                  .unmarshalFilter(featureType, filters);
 +                  .parseFilter(featureType, filters);
            if (!filter.isEmpty())
            {
              fr.setFeatureFilter(featureType, filter);
           * restore feature colour scheme
           */
          Color maxColour = new Color(setting.getColour());
 -        if (setting.hasMincolour())
 +        if (setting.getMincolour() != null)
          {
            /*
             * minColour is always set unless a simple colour
             * (including for colour by label though it doesn't use it)
             */
 -          Color minColour = new Color(setting.getMincolour());
 +          Color minColour = new Color(setting.getMincolour().intValue());
            Color noValueColour = minColour;
            NoValueColour noColour = setting.getNoValueColour();
            if (noColour == NoValueColour.NONE)
            {
              noValueColour = maxColour;
            }
 -          float min = setting.hasMin() ? setting.getMin() : 0f;
 -          float max = setting.hasMin() ? setting.getMax() : 1f;
 +          float min = safeFloat(safeFloat(setting.getMin()));
 +          float max = setting.getMax() == null ? 1f
 +                  : setting.getMax().floatValue();
-           FeatureColourI gc = new FeatureColour(minColour, maxColour,
+           FeatureColourI gc = new FeatureColour(maxColour, minColour,
 -                  maxColour, noValueColour, min, max);
 -          if (setting.getAttributeNameCount() > 0)
++                  maxColour,
 +                  noValueColour, min, max);
 +          if (setting.getAttributeName().size() > 0)
            {
 -            gc.setAttributeName(setting.getAttributeName());
 +            gc.setAttributeName(setting.getAttributeName().toArray(
 +                    new String[setting.getAttributeName().size()]));
            }
 -          if (setting.hasThreshold())
 +          if (setting.getThreshold() != null)
            {
 -            gc.setThreshold(setting.getThreshold());
 -            int threshstate = setting.getThreshstate();
 +            gc.setThreshold(setting.getThreshold().floatValue());
 +            int threshstate = safeInt(setting.getThreshstate());
              // -1 = None, 0 = Below, 1 = Above threshold
              if (threshstate == 0)
              {
              }
            }
            gc.setAutoScaled(true); // default
 -          if (setting.hasAutoScale())
 +          if (setting.isAutoScale() != null)
            {
 -            gc.setAutoScaled(setting.getAutoScale());
 +            gc.setAutoScaled(setting.isAutoScale());
            }
 -          if (setting.hasColourByLabel())
 +          if (setting.isColourByLabel() != null)
            {
 -            gc.setColourByLabel(setting.getColourByLabel());
 +            gc.setColourByLabel(setting.isColourByLabel());
            }
            // and put in the feature colour table.
            featureColours.put(featureType, gc);
                    new FeatureColour(maxColour));
          }
          renderOrder[fs] = featureType;
 -        if (setting.hasOrder())
 +        if (setting.getOrder() != null)
          {
 -          featureOrder.put(featureType, setting.getOrder());
 +          featureOrder.put(featureType, setting.getOrder().floatValue());
          }
          else
          {
            featureOrder.put(featureType, new Float(
 -                  fs / jms.getFeatureSettings().getSettingCount()));
 +                  fs / jm.getFeatureSettings().getSetting().size()));
          }
 -        if (setting.getDisplay())
 +        if (safeBoolean(setting.isDisplay()))
          {
            fdi.setVisible(featureType);
          }
        }
        Map<String, Boolean> fgtable = new Hashtable<>();
 -      for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
 +      for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
        {
 -        Group grp = jms.getFeatureSettings().getGroup(gs);
 -        fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
 +        Group grp = jm.getFeatureSettings().getGroup().get(gs);
 +        fgtable.put(grp.getName(), new Boolean(grp.isDisplay()));
        }
        // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
        // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
        fr.transferSettings(frs);
      }
  
 -    if (view.getHiddenColumnsCount() > 0)
 +    if (view.getHiddenColumns().size() > 0)
      {
 -      for (int c = 0; c < view.getHiddenColumnsCount(); c++)
 +      for (int c = 0; c < view.getHiddenColumns().size(); c++)
        {
 -        af.viewport.hideColumns(view.getHiddenColumns(c).getStart(),
 -                view.getHiddenColumns(c).getEnd() // +1
 -        );
 +        final HiddenColumns hc = view.getHiddenColumns().get(c);
 +        viewport.hideColumns(safeInt(hc.getStart()),
 +                safeInt(hc.getEnd()) /* +1 */);
        }
      }
      if (view.getCalcIdParam() != null)
        {
          if (calcIdParam != null)
          {
 -          if (recoverCalcIdParam(calcIdParam, af.viewport))
 +          if (recoverCalcIdParam(calcIdParam, viewport))
            {
            }
            else
          }
        }
      }
 -    af.setMenusFromViewport(af.viewport);
 +    af.setMenusFromViewport(viewport);
      af.setTitle(view.getTitle());
      // TODO: we don't need to do this if the viewport is aready visible.
      /*
      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);
     * @param viewAnnColour
     * @param af
     * @param al
 -   * @param jms
 +   * @param model
     * @param checkGroupAnnColour
     * @return
     */
    private ColourSchemeI constructAnnotationColour(
 -          AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al,
 -          JalviewModelSequence jms, boolean checkGroupAnnColour)
 +          AnnotationColourScheme viewAnnColour, AlignFrame af,
 +          AlignmentI al, JalviewModel model, boolean checkGroupAnnColour)
    {
      boolean propagateAnnColour = false;
 -    AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
 +    AlignmentI annAlignment = af != null ? af.getViewport().getAlignment()
 +            : al;
      if (checkGroupAnnColour && al.getGroups() != null
              && al.getGroups().size() > 0)
      {
      }
      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(jms, viewAnnColour.getColourScheme()),
 -              viewAnnColour.getAboveThreshold());
 +              getUserColourScheme(model, viewAnnColour.getColourScheme()),
 +              safeInt(viewAnnColour.getAboveThreshold()));
      }
      else
      {
        cs = new AnnotationColourGradient(matchedAnnotation,
                ColourSchemeProperty.getColourScheme(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);
  
  
          AnnotationColourGradient groupScheme = new AnnotationColourGradient(
                  matchedAnnotation, sg.getColourScheme(),
 -                viewAnnColour.getAboveThreshold());
 +                safeInt(viewAnnColour.getAboveThreshold()));
          sg.setColourScheme(groupScheme);
          groupScheme.setSeqAssociated(perSequenceOnly);
          groupScheme.setPredefinedColours(useOriginalColours);
      {
        return false;
      }
 -    String id;
 -    if (skipList.containsKey(
 -            id = object.getJalviewModelSequence().getViewport()[0]
 -                    .getSequenceSetId()))
 +    String id = object.getViewport().get(0).getSequenceSetId();
 +    if (skipList.containsKey(id))
      {
        if (Cache.log != null && Cache.log.isDebugEnabled())
        {
    }
  
    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();
      }
 -    for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
 +    for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
      {
 -      Sequence vamsasSeq = vamsasSet.getSequence(i);
 +      Sequence vamsasSeq = vamsasSet.getSequence().get(i);
        ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
      }
      // create a new dataset
      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
  
    private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
    {
 -    for (int d = 0; d < sequence.getDBRefCount(); d++)
 +    for (int d = 0; d < sequence.getDBRef().size(); d++)
      {
 -      DBRef dr = sequence.getDBRef(d);
 +      DBRef dr = sequence.getDBRef().get(d);
        jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
 -              sequence.getDBRef(d).getSource(),
 -              sequence.getDBRef(d).getVersion(),
 -              sequence.getDBRef(d).getAccessionId());
 +              dr.getSource(), dr.getVersion(), dr.getAccessionId());
        if (dr.getMapping() != null)
        {
          entry.setMap(addMapping(dr.getMapping()));
    {
      SequenceI dsto = null;
      // Mapping m = dr.getMapping();
 -    int fr[] = new int[m.getMapListFromCount() * 2];
 -    Enumeration f = m.enumerateMapListFrom();
 -    for (int _i = 0; f.hasMoreElements(); _i += 2)
 +    int fr[] = new int[m.getMapListFrom().size() * 2];
 +    Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
 +    for (int _i = 0; from.hasNext(); _i += 2)
      {
 -      MapListFrom mf = (MapListFrom) f.nextElement();
 +      MapListFrom mf = from.next();
        fr[_i] = mf.getStart();
        fr[_i + 1] = mf.getEnd();
      }
 -    int fto[] = new int[m.getMapListToCount() * 2];
 -    f = m.enumerateMapListTo();
 -    for (int _i = 0; f.hasMoreElements(); _i += 2)
 +    int fto[] = new int[m.getMapListTo().size() * 2];
 +    Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
 +    for (int _i = 0; to.hasNext(); _i += 2)
      {
 -      MapListTo mf = (MapListTo) f.nextElement();
 +      MapListTo mf = to.next();
        fto[_i] = mf.getStart();
        fto[_i + 1] = mf.getEnd();
      }
      jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
 -            fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
 -    if (m.getMappingChoice() != null)
 +            fto, m.getMapFromUnit().intValue(),
 +            m.getMapToUnit().intValue());
 +
 +    /*
 +     * (optional) choice of dseqFor or Sequence
 +     */
 +    if (m.getDseqFor() != null)
      {
 -      MappingChoice mc = m.getMappingChoice();
 -      if (mc.getDseqFor() != null)
 +      String dsfor = m.getDseqFor();
 +      if (seqRefIds.containsKey(dsfor))
        {
 -        String dsfor = "" + mc.getDseqFor();
 -        if (seqRefIds.containsKey(dsfor))
 -        {
 -          /**
 -           * recover from hash
 -           */
 -          jmap.setTo(seqRefIds.get(dsfor));
 -        }
 -        else
 -        {
 -          frefedSequence.add(newMappingRef(dsfor, jmap));
 -        }
 +        /*
 +         * recover from hash
 +         */
 +        jmap.setTo(seqRefIds.get(dsfor));
        }
        else
        {
 -        /**
 -         * local sequence definition
 +        frefedSequence.add(newMappingRef(dsfor, jmap));
 +      }
 +    }
 +    else if (m.getSequence() != null)
 +    {
 +      /*
 +       * local sequence definition
 +       */
 +      Sequence ms = m.getSequence();
 +      SequenceI djs = null;
 +      String sqid = ms.getDsseqid();
 +      if (sqid != null && sqid.length() > 0)
 +      {
 +        /*
 +         * recover dataset sequence
           */
 -        Sequence ms = mc.getSequence();
 -        SequenceI djs = null;
 -        String sqid = ms.getDsseqid();
 -        if (sqid != null && sqid.length() > 0)
 -        {
 -          /*
 -           * recover dataset sequence
 -           */
 -          djs = seqRefIds.get(sqid);
 -        }
 -        else
 -        {
 -          System.err.println(
 -                  "Warning - making up dataset sequence id for DbRef sequence map reference");
 -          sqid = ((Object) ms).toString(); // make up a new hascode for
 -          // undefined dataset sequence hash
 -          // (unlikely to happen)
 -        }
 -
 -        if (djs == null)
 -        {
 -          /**
 -           * make a new dataset sequence and add it to refIds hash
 -           */
 -          djs = new jalview.datamodel.Sequence(ms.getName(),
 -                  ms.getSequence());
 -          djs.setStart(jmap.getMap().getToLowest());
 -          djs.setEnd(jmap.getMap().getToHighest());
 -          djs.setVamsasId(uniqueSetSuffix + sqid);
 -          jmap.setTo(djs);
 -          incompleteSeqs.put(sqid, djs);
 -          seqRefIds.put(sqid, djs);
 +        djs = seqRefIds.get(sqid);
 +      }
 +      else
 +      {
 +        System.err.println(
 +                "Warning - making up dataset sequence id for DbRef sequence map reference");
 +        sqid = ((Object) ms).toString(); // make up a new hascode for
 +        // undefined dataset sequence hash
 +        // (unlikely to happen)
 +      }
  
 -        }
 -        jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
 -        addDBRefs(djs, ms);
 +      if (djs == null)
 +      {
 +        /**
 +         * make a new dataset sequence and add it to refIds hash
 +         */
 +        djs = new jalview.datamodel.Sequence(ms.getName(),
 +                ms.getSequence());
 +        djs.setStart(jmap.getMap().getToLowest());
 +        djs.setEnd(jmap.getMap().getToHighest());
 +        djs.setVamsasId(uniqueSetSuffix + sqid);
 +        jmap.setTo(djs);
 +        incompleteSeqs.put(sqid, djs);
 +        seqRefIds.put(sqid, djs);
  
        }
 +      jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
 +      addDBRefs(djs, ms);
 +
      }
 -    return (jmap);
  
 +    return jmap;
    }
  
    /**
      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.getJalviewModelSequence().getViewport(0).setId(null);
 +    jm.getViewport().get(0).setId(null);
      // we don't overwrite the view we just copied
  
      if (this.frefedSequence == null)
      viewportsAdded.clear();
  
      AlignFrame af = loadFromObject(jm, null, false, null);
 -    af.alignPanels.clear();
 +    af.getAlignPanels().clear();
      af.closeMenuItem_actionPerformed(true);
  
      /*
    }
  
    /**
 +   * 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 jalview.schemabinding.version2.Colour marshalColour(
 +  public static Colour marshalColour(
            String featureType, FeatureColourI fcol)
    {
 -    jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
 +    Colour col = new Colour();
      if (fcol.isSimpleColour())
      {
        col.setRGB(Format.getHexString(fcol.getColour()));
        col.setAutoScale(fcol.isAutoScaled());
        col.setThreshold(fcol.getThreshold());
        col.setColourByLabel(fcol.isColourByLabel());
 -      col.setThreshType(fcol.isAboveThreshold() ? ColourThreshTypeType.ABOVE
 -              : (fcol.isBelowThreshold() ? ColourThreshTypeType.BELOW
 -                      : ColourThreshTypeType.NONE));
 +      col.setThreshType(fcol.isAboveThreshold() ? ThresholdType.ABOVE
 +              : (fcol.isBelowThreshold() ? ThresholdType.BELOW
 +                      : ThresholdType.NONE));
        if (fcol.isColourByAttribute())
        {
 -        col.setAttributeName(fcol.getAttributeName());
 +        final String[] attName = fcol.getAttributeName();
 +        col.getAttributeName().add(attName[0]);
 +        if (attName.length > 1)
 +        {
 +          col.getAttributeName().add(attName[1]);
 +        }
        }
        Color noColour = fcol.getNoColour();
        if (noColour == null)
     * @param and
     *          if true, conditions are and-ed, else or-ed
     */
 -  protected static MatcherSet marshalFilter(FeatureMatcherI firstMatcher,
 -          Iterator<FeatureMatcherI> filters, boolean and)
 +  public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
 +          FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
 +          boolean and)
    {
 -    MatcherSet result = new MatcherSet();
 +    jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
    
      if (filters.hasNext())
      {
         */
        CompoundMatcher compound = new CompoundMatcher();
        compound.setAnd(and);
 -      MatcherSet matcher1 = marshalFilter(firstMatcher,
 -              Collections.emptyIterator(), and);
 -      compound.addMatcherSet(matcher1);
 +      jalview.xml.binding.jalview.FeatureMatcherSet matcher1 = marshalFilter(
 +              firstMatcher, Collections.emptyIterator(), and);
 +      // compound.addMatcherSet(matcher1);
 +      compound.getMatcherSet().add(matcher1);
        FeatureMatcherI nextMatcher = filters.next();
 -      MatcherSet matcher2 = marshalFilter(nextMatcher, filters, and);
 -      compound.addMatcherSet(matcher2);
 +      jalview.xml.binding.jalview.FeatureMatcherSet matcher2 = marshalFilter(
 +              nextMatcher, filters, and);
 +      // compound.addMatcherSet(matcher2);
 +      compound.getMatcherSet().add(matcher2);
        result.setCompoundMatcher(compound);
      }
      else
        /*
         * single condition matcher
         */
 -      MatchCondition matcherModel = new MatchCondition();
 +      // MatchCondition matcherModel = new MatchCondition();
 +      jalview.xml.binding.jalview.FeatureMatcher matcherModel = new jalview.xml.binding.jalview.FeatureMatcher();
        matcherModel.setCondition(
                firstMatcher.getMatcher().getCondition().getStableName());
        matcherModel.setValue(firstMatcher.getMatcher().getPattern());
        if (firstMatcher.isByAttribute())
        {
 -        matcherModel.setBy(FeatureMatcherByType.BYATTRIBUTE);
 -        matcherModel.setAttributeName(firstMatcher.getAttribute());
 +        matcherModel.setBy(FilterBy.BY_ATTRIBUTE);
 +        // matcherModel.setAttributeName(firstMatcher.getAttribute());
 +        String[] attName = firstMatcher.getAttribute();
 +        matcherModel.getAttributeName().add(attName[0]); // attribute
 +        if (attName.length > 1)
 +        {
 +          matcherModel.getAttributeName().add(attName[1]); // sub-attribute
 +        }
        }
        else if (firstMatcher.isByLabel())
        {
 -        matcherModel.setBy(FeatureMatcherByType.BYLABEL);
 +        matcherModel.setBy(FilterBy.BY_LABEL);
        }
        else if (firstMatcher.isByScore())
        {
 -        matcherModel.setBy(FeatureMatcherByType.BYSCORE);
 +        matcherModel.setBy(FilterBy.BY_SCORE);
        }
        result.setMatchCondition(matcherModel);
      }
     * @param matcherSetModel
     * @return
     */
 -  protected static FeatureMatcherSetI unmarshalFilter(
 -          String featureType, MatcherSet matcherSetModel)
 +  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
     * @throws IllegalStateException
     *           if AND and OR conditions are mixed
     */
 -  protected static void unmarshalFilterConditions(
 -          FeatureMatcherSetI matcherSet, MatcherSet matcherSetModel,
 +  protected static void parseFilterConditions(
 +          FeatureMatcherSetI matcherSet,
 +          jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
            boolean and)
    {
 -    MatchCondition mc = matcherSetModel.getMatchCondition();
 +    jalview.xml.binding.jalview.FeatureMatcher mc = matcherSetModel
 +            .getMatchCondition();
      if (mc != null)
      {
        /*
         * single condition
         */
 -      FeatureMatcherByType filterBy = mc.getBy();
 +      FilterBy filterBy = mc.getBy();
        Condition cond = Condition.fromString(mc.getCondition());
        String pattern = mc.getValue();
        FeatureMatcherI matchCondition = null;
 -      if (filterBy == FeatureMatcherByType.BYLABEL)
 +      if (filterBy == FilterBy.BY_LABEL)
        {
          matchCondition = FeatureMatcher.byLabel(cond, pattern);
        }
 -      else if (filterBy == FeatureMatcherByType.BYSCORE)
 +      else if (filterBy == FilterBy.BY_SCORE)
        {
          matchCondition = FeatureMatcher.byScore(cond, pattern);
    
        }
 -      else if (filterBy == FeatureMatcherByType.BYATTRIBUTE)
 +      else if (filterBy == FilterBy.BY_ATTRIBUTE)
        {
 -        String[] attNames = mc.getAttributeName();
 +        final List<String> attributeName = mc.getAttributeName();
 +        String[] attNames = attributeName
 +                .toArray(new String[attributeName.size()]);
          matchCondition = FeatureMatcher.byAttribute(cond, pattern,
                  attNames);
        }
        /*
         * compound condition
         */
 -      MatcherSet[] matchers = matcherSetModel.getCompoundMatcher()
 -              .getMatcherSet();
 -      boolean anded = matcherSetModel.getCompoundMatcher().getAnd();
 -      if (matchers.length == 2)
 +      List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
 +              .getCompoundMatcher().getMatcherSet();
 +      boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
 +      if (matchers.size() == 2)
        {
 -        unmarshalFilterConditions(matcherSet, matchers[0], anded);
 -        unmarshalFilterConditions(matcherSet, matchers[1], anded);
 +        parseFilterConditions(matcherSet, matchers.get(0), anded);
 +        parseFilterConditions(matcherSet, matchers.get(1), anded);
        }
        else
        {
     * @param colourModel
     * @return
     */
 -  protected static FeatureColourI unmarshalColour(
 -          jalview.schemabinding.version2.Colour colourModel)
 +  public static FeatureColourI parseColour(Colour colourModel)
    {
      FeatureColourI colour = null;
    
 -    if (colourModel.hasMax())
 +    if (colourModel.getMax() != null)
      {
        Color mincol = null;
        Color maxcol = null;
          noValueColour = maxcol;
        }
    
-       colour = new FeatureColour(mincol, maxcol, noValueColour,
+       colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
 -              colourModel.getMin(), colourModel.getMax());
 -      String[] attributes = colourModel.getAttributeName();
 +              safeFloat(colourModel.getMin()),
 +              safeFloat(colourModel.getMax()));
 +      final List<String> attributeName = colourModel.getAttributeName();
 +      String[] attributes = attributeName
 +              .toArray(new String[attributeName.size()]);
        if (attributes != null && attributes.length > 0)
        {
          colour.setAttributeName(attributes);
        }
 -      if (colourModel.hasAutoScale())
 +      if (colourModel.isAutoScale() != null)
        {
 -        colour.setAutoScaled(colourModel.getAutoScale());
 +        colour.setAutoScaled(colourModel.isAutoScale().booleanValue());
        }
 -      if (colourModel.hasColourByLabel())
 +      if (colourModel.isColourByLabel() != null)
        {
 -        colour.setColourByLabel(colourModel.getColourByLabel());
 +        colour.setColourByLabel(
 +                colourModel.isColourByLabel().booleanValue());
        }
 -      if (colourModel.hasThreshold())
 +      if (colourModel.getThreshold() != null)
        {
 -        colour.setThreshold(colourModel.getThreshold());
 +        colour.setThreshold(colourModel.getThreshold().floatValue());
        }
 -      ColourThreshTypeType ttyp = colourModel.getThreshType();
 -      if (ttyp != null)
 +      ThresholdType ttyp = colourModel.getThreshType();
 +      if (ttyp == ThresholdType.ABOVE)
        {
 -        if (ttyp == ColourThreshTypeType.ABOVE)
 -        {
 -          colour.setAboveThreshold(true);
 -        }
 -        else if (ttyp == ColourThreshTypeType.BELOW)
 -        {
 -          colour.setBelowThreshold(true);
 -        }
 +        colour.setAboveThreshold(true);
 +      }
 +      else if (ttyp == ThresholdType.BELOW)
 +      {
 +        colour.setBelowThreshold(true);
        }
      }
      else
@@@ -321,8 -321,8 +321,8 @@@ public class FeatureColour implements F
        } catch (Exception e)
        {
          throw new IllegalArgumentException(
 -                "Couldn't parse the minimum value for graduated colour ("
 -                        + descriptor + ")");
 +                "Couldn't parse the minimum value for graduated colour ('"
 +                        + minval + "')");
        }
        try
        {
        Color maxColour = ColorUtils.parseColourString(maxcol);
        Color noColour = noValueColour.equals(NO_VALUE_MAX) ? maxColour
                : (noValueColour.equals(NO_VALUE_NONE) ? null : minColour);
-       featureColour = new FeatureColour(minColour, maxColour, noColour, min,
-               max);
+       featureColour = new FeatureColour(maxColour, minColour, maxColour,
+               noColour, min, max);
        featureColour.setColourByLabel(minColour == null);
        featureColour.setAutoScaled(autoScaled);
        if (byAttribute)
    }
  
    /**
-    * Constructor given a simple colour
+    * Constructor given a simple colour. This also 'primes' a graduated colour
+    * range, where the maximum colour is the given simple colour, and the minimum
+    * colour a paler shade of it. This is for convenience when switching from a
+    * simple colour to a graduated colour scheme.
     * 
     * @param c
     */
    public FeatureColour(Color c)
    {
-     minColour = Color.WHITE;
-     maxColour = Color.BLACK;
-     noColour = DEFAULT_NO_COLOUR;
-     minRed = 0f;
-     minGreen = 0f;
-     minBlue = 0f;
-     deltaRed = 0f;
-     deltaGreen = 0f;
-     deltaBlue = 0f;
-     colour = c;
-   }
+     /*
+      * set max colour to the simple colour, min colour to a paler shade of it
+      */
+     this(c, c == null ? Color.white : ColorUtils.bleachColour(c, 0.9f),
+             c == null ? Color.black : c, DEFAULT_NO_COLOUR, 0, 0);
  
-   /**
-    * Constructor given a colour range and a score range, defaulting 'no value
-    * colour' to be the same as minimum colour
-    * 
-    * @param low
-    * @param high
-    * @param min
-    * @param max
-    */
-   public FeatureColour(Color low, Color high, float min, float max)
-   {
-     this(low, high, low, min, max);
+     /*
+      * but enforce simple colour for now!
+      */
+     setGraduatedColour(false);
    }
  
    /**
    }
  
    /**
-    * Copy constructor with new min/max ranges
-    * 
-    * @param fc
-    * @param min
-    * @param max
-    */
-   public FeatureColour(FeatureColour fc, float min, float max)
-   {
-     this(fc);
-     updateBounds(min, max);
-   }
-   /**
-    * Constructor for a graduated colour
+    * Constructor that sets both simple and graduated colour values. This allows
+    * alternative colour schemes to be 'preserved' while switching between them
+    * to explore their effects on the visualisation.
+    * <p>
+    * This sets the colour scheme to 'graduated' by default. Override this if
+    * wanted by calling <code>setGraduatedColour(false)</code> for a simple
+    * colour, or <code>setColourByLabel(true)</code> for colour by label.
     * 
+    * @param myColour
     * @param low
     * @param high
     * @param noValueColour
     * @param min
     * @param max
     */
-   public FeatureColour(Color low, Color high, Color noValueColour,
-           float min, float max)
+   public FeatureColour(Color myColour, Color low, Color high,
+           Color noValueColour, float min, float max)
    {
      if (low == null)
      {
      {
        high = Color.black;
      }
-     graduatedColour = true;
-     colour = null;
+     colour = myColour;
      minColour = low;
      maxColour = high;
+     setGraduatedColour(true);
      noColour = noValueColour;
      threshold = Float.NaN;
      isHighToLow = min >= max;
     * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
     * false.
     */
-   void setGraduatedColour(boolean b)
+   public void setGraduatedColour(boolean b)
    {
      graduatedColour = b;
      if (b)
  
    /**
     * Returns the colour for the given instance of the feature. This may be a
 -   * simple colour, a colour generated from the feature description (if
 -   * isColourByLabel()), or a colour derived from the feature score (if
 -   * isGraduatedColour()).
 +   * simple colour, a colour generated from the feature description or other
 +   * attribute (if isColourByLabel()), or a colour derived from the feature
 +   * score or other attribute (if isGraduatedColour()).
 +   * <p>
 +   * Answers null if feature score (or attribute) value lies outside a
 +   * configured threshold.
     * 
     * @param feature
     * @return
          sb.append(BAR).append(Format.getHexString(getMinColour()))
                  .append(BAR);
          sb.append(Format.getHexString(getMaxColour())).append(BAR);
-         String noValue = minColour.equals(noColour) ? NO_VALUE_MIN
-                 : (maxColour.equals(noColour) ? NO_VALUE_MAX
-                         : NO_VALUE_NONE);
+         
+         /*
+          * 'no value' colour should be null, min or max colour;
+          * if none of these, coerce to minColour
+          */
+         String noValue = NO_VALUE_MIN;
+         if (maxColour.equals(noColour))
+         {
+           noValue = NO_VALUE_MAX;
+         }
+         if (noColour == null)
+         {
+           noValue = NO_VALUE_NONE;
+         }
          sb.append(noValue).append(BAR);
          if (!isAutoScaled())
          {
      attributeName = name;
    }
  
 +  @Override
 +  public boolean isOutwithThreshold(SequenceFeature feature)
 +  {
 +    if (!isGraduatedColour())
 +    {
 +      return false;
 +    }
 +    float scr = feature.getScore();
 +    if (attributeName != null)
 +    {
 +      try
 +      {
 +        String attVal = feature.getValueAsString(attributeName);
 +        scr = Float.valueOf(attVal);
 +      } catch (Throwable e)
 +      {
 +        scr = Float.NaN;
 +      }
 +    }
 +    if (Float.isNaN(scr))
 +    {
 +      return false;
 +    }
 +
 +    return ((isAboveThreshold() && scr <= threshold)
 +            || (isBelowThreshold() && scr >= threshold));
 +  }
 +
  }
@@@ -118,7 -118,7 +118,10 @@@ public class ConsensusThread extends Al
    protected void eraseConsensus(int aWidth)
    {
      AlignmentAnnotation consensus = getConsensusAnnotation();
--    consensus.annotations = new Annotation[aWidth];
++    if (consensus != null)
++    {
++      consensus.annotations = new Annotation[aWidth];
++    }
      AlignmentAnnotation gap = getGapAnnotation();
      if (gap != null)
      {
@@@ -20,7 -20,6 +20,7 @@@
   */
  package jalview.gui;
  
 +import static org.junit.Assert.assertNotEquals;
  import static org.testng.Assert.assertEquals;
  import static org.testng.Assert.assertFalse;
  import static org.testng.Assert.assertNotSame;
@@@ -39,7 -38,7 +39,7 @@@ import jalview.datamodel.SequenceGroup
  import jalview.datamodel.SequenceI;
  import jalview.io.DataSourceType;
  import jalview.io.FileLoader;
 -import jalview.io.Jalview2xmlTests;
 +import jalview.project.Jalview2xmlTests;
  import jalview.renderer.ResidueShaderI;
  import jalview.schemes.BuriedColourScheme;
  import jalview.schemes.FeatureColour;
@@@ -119,7 -118,8 +119,8 @@@ public class AlignFrameTes
       * seq1 feature in columns 1-5 is hidden
       * seq2 feature in columns 6-10 is shown
       */
-     FeatureColourI fc = new FeatureColour(Color.red, Color.blue, 0f, 10f);
+     FeatureColourI fc = new FeatureColour(null, Color.red, Color.blue, null,
+             0f, 10f);
      fc.setAboveThreshold(true);
      fc.setThreshold(5f);
      alignFrame.getFeatureRenderer().setColour("Metal", fc);
      sp.valueChanged(22);
      assertEquals(av2.getResidueShading().getConservationInc(), 22);
    }
 +
 +  /**
 +   * Verify that making a New View preserves the dataset reference for the
 +   * alignment. Otherwise, see a 'duplicate jar entry' reference when trying to
 +   * save alignments with multiple views, and codon mappings will not be shared
 +   * across all panels in a split frame.
 +   * 
 +   * @see Jalview2xmlTests#testStoreAndRecoverColourThresholds()
 +   */
 +  @Test(groups = "Functional")
 +  public void testNewView_dsRefPreserved()
 +  {
 +    AlignViewport av = af.getViewport();
 +    AlignmentI al = av.getAlignment();
 +    AlignmentI original_ds = al.getDataset();
 +    af.newView_actionPerformed(null);
 +    assertNotEquals("New view didn't select the a new panel", av,
 +            af.getViewport());
 +    org.testng.Assert.assertEquals(original_ds,
 +            af.getViewport().getAlignment().getDataset(),
 +            "Dataset was not preserved in new view");
 +  }
  }
@@@ -45,12 -45,12 +45,12 @@@ import jalview.gui.JvOptionPane
  import jalview.schemes.FeatureColour;
  import jalview.structure.StructureSelectionManager;
  import jalview.util.matcher.Condition;
 +import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 +import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
  
  import java.awt.Color;
  import java.io.File;
  import java.io.IOException;
 -import java.util.ArrayList;
 -import java.util.Arrays;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
@@@ -475,22 -475,24 +475,22 @@@ public class FeaturesFileTes
       * first with no features displayed, exclude non-positional features
       */
      FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
 -    Map<String, FeatureColourI> visible = fr.getDisplayedFeatureCols();
 -    List<String> visibleGroups = new ArrayList<>(
 -            Arrays.asList(new String[] {}));
 -    String exported = featuresFile.printJalviewFormat(
 -            al.getSequencesArray(), visible, null, visibleGroups, false);
 +    String exported = featuresFile
 +            .printJalviewFormat(al.getSequencesArray(), fr, false);
      String expected = "No Features Visible";
      assertEquals(expected, exported);
  
      /*
 -     * include non-positional features
 +     * include non-positional features, but still no positional features
       */
 -    visibleGroups.add("uniprot");
 -    exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
 -            visible, null, visibleGroups, true);
 -    expected = "Cath\tFER_CAPAA\t-1\t0\t0\tDomain\t0.0\n"
 -            + "desc1\tFER_CAPAN\t-1\t0\t0\tPfam\t1.3\n"
 -            + "desc3\tFER1_SOLLC\t-1\t0\t0\tPfam\n" // NaN is not output
 -            + "\nSTARTGROUP\tuniprot\nENDGROUP\tuniprot\n";
 +    fr.setGroupVisibility("uniprot", true);
 +    exported = featuresFile.printJalviewFormat(al.getSequencesArray(), fr,
 +            true);
 +    expected = "\nSTARTGROUP\tuniprot\n"
 +            + "Cath\tFER_CAPAA\t-1\t0\t0\tDomain\t0.0\n"
 +            + "ENDGROUP\tuniprot\n\n"
 +            + "desc1\tFER_CAPAN\t-1\t0\t0\tPfam\t1.3\n\n"
 +            + "desc3\tFER1_SOLLC\t-1\t0\t0\tPfam\n"; // NaN is not output
      assertEquals(expected, exported);
  
      /*
       */
      fr.setVisible("METAL");
      fr.setVisible("GAMMA-TURN");
 -    visible = fr.getDisplayedFeatureCols();
 -    exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
 -            visible, null, visibleGroups, false);
 +    exported = featuresFile.printJalviewFormat(al.getSequencesArray(), fr,
 +            false);
      expected = "METAL\tcc9900\n"
              + "GAMMA-TURN\tscore|ff0000|00ffff|noValueMin|20.0|95.0|below|66.0\n"
              + "\nSTARTGROUP\tuniprot\n"
       * now set Pfam visible
       */
      fr.setVisible("Pfam");
 -    visible = fr.getDisplayedFeatureCols();
 -    exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
 -            visible, null, visibleGroups, false);
 +    exported = featuresFile.printJalviewFormat(al.getSequencesArray(), fr,
 +            false);
      /*
 -     * features are output within group, ordered by sequence and by type
 +     * features are output within group, ordered by sequence and type
       */
      expected = "METAL\tcc9900\n"
              + "Pfam\tff0000\n"
              + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
              + "<html>Pfam domain<a href=\"http://pfam.xfam.org/family/PF00111\">Pfam_3_4</a></html>\tFER_CAPAA\t-1\t20\t20\tPfam\t0.0\n"
              + "ENDGROUP\tuniprot\n"
 -            // null / empty group features output after features in named
 -            // groups:
 +            // null / empty group features are output after named groups
 +            + "\ndesc2\tFER_CAPAN\t-1\t4\t9\tPfam\n"
 +            + "\ndesc4\tFER1_SOLLC\t-1\t5\t8\tPfam\t-2.6\n";
 +    assertEquals(expected, exported);
 +
 +    /*
 +     * hide uniprot group
 +     */
 +    fr.setGroupVisibility("uniprot", false);
 +    expected = "METAL\tcc9900\n" + "Pfam\tff0000\n"
 +            + "GAMMA-TURN\tscore|ff0000|00ffff|noValueMin|20.0|95.0|below|66.0\n"
 +            + "\ndesc2\tFER_CAPAN\t-1\t4\t9\tPfam\n"
 +            + "\ndesc4\tFER1_SOLLC\t-1\t5\t8\tPfam\t-2.6\n";
 +    exported = featuresFile.printJalviewFormat(al.getSequencesArray(), fr,
 +            false);
 +    assertEquals(expected, exported);
 +
 +    /*
 +     * include non-positional (overrides group not shown)
 +     */
 +    exported = featuresFile.printJalviewFormat(al.getSequencesArray(), fr,
 +            true);
 +    expected = "METAL\tcc9900\n" + "Pfam\tff0000\n"
 +            + "GAMMA-TURN\tscore|ff0000|00ffff|noValueMin|20.0|95.0|below|66.0\n"
 +            + "\nSTARTGROUP\tuniprot\n"
 +            + "Cath\tFER_CAPAA\t-1\t0\t0\tDomain\t0.0\n"
 +            + "ENDGROUP\tuniprot\n"
 +            + "\ndesc1\tFER_CAPAN\t-1\t0\t0\tPfam\t1.3\n"
              + "desc2\tFER_CAPAN\t-1\t4\t9\tPfam\n"
 +            + "\ndesc3\tFER1_SOLLC\t-1\t0\t0\tPfam\n"
              + "desc4\tFER1_SOLLC\t-1\t5\t8\tPfam\t-2.6\n";
      assertEquals(expected, exported);
    }
       * no features
       */
      FeaturesFile featuresFile = new FeaturesFile();
 -    FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
 -    Map<String, FeatureColourI> visible = new HashMap<>();
 -    List<String> visibleGroups = new ArrayList<>(
 -            Arrays.asList(new String[] {}));
 +    FeatureRendererModel fr = (FeatureRendererModel) af.alignPanel
 +            .getFeatureRenderer();
      String exported = featuresFile.printGffFormat(al.getSequencesArray(),
 -            visible, visibleGroups, false);
 +            fr, false);
      String gffHeader = "##gff-version 2\n";
      assertEquals(gffHeader, exported);
 -    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 -            visibleGroups, true);
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
 +            true);
      assertEquals(gffHeader, exported);
  
      /*
      al.getSequenceAt(1).addSequenceFeature(sf);
  
      /*
 +     * 'discover' features then hide all feature types
 +     */
 +    fr.findAllFeatures(true);
 +    FeatureSettingsBean[] data = new FeatureSettingsBean[4];
 +    FeatureColourI fc = new FeatureColour(Color.PINK);
 +    data[0] = new FeatureSettingsBean("Domain", fc, null, false);
 +    data[1] = new FeatureSettingsBean("METAL", fc, null, false);
 +    data[2] = new FeatureSettingsBean("GAMMA-TURN", fc, null, false);
 +    data[3] = new FeatureSettingsBean("Pfam", fc, null, false);
 +    fr.setFeaturePriority(data);
 +
 +    /*
       * with no features displayed, exclude non-positional features
       */
 -    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 -            visibleGroups, false);
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
 +            false);
      assertEquals(gffHeader, exported);
  
      /*
       * include non-positional features
       */
 -    visibleGroups.add("Uniprot");
 -    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 -            visibleGroups, true);
 +    fr.setGroupVisibility("Uniprot", true);
 +    fr.setGroupVisibility("s3dm", false);
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
 +            true);
      String expected = gffHeader
              + "FER_CAPAA\tUniprot\tDomain\t0\t0\t0.0\t.\t.\n";
      assertEquals(expected, exported);
       */
      fr.setVisible("METAL");
      fr.setVisible("GAMMA-TURN");
 -    visible = fr.getDisplayedFeatureCols();
 -    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 -            visibleGroups, false);
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
 +            false);
      // METAL feature has null group: description used for column 2
      expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n";
      assertEquals(expected, exported);
      /*
       * set s3dm group visible
       */
 -    visibleGroups.add("s3dm");
 -    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 -            visibleGroups, false);
 +    fr.setGroupVisibility("s3dm", true);
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
 +            false);
      // METAL feature has null group: description used for column 2
      expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
              + "FER_CAPAN\ts3dm\tGAMMA-TURN\t36\t38\t2.1\t.\t.\n";
       * now set Pfam visible
       */
      fr.setVisible("Pfam");
 -    visible = fr.getDisplayedFeatureCols();
 -    exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
 -            visibleGroups, false);
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
 +            false);
      // Pfam feature columns include strand(+), phase(2), attributes
      expected = gffHeader
              + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
      featureFilters.put("pfam", filter2);
      visible.put("foobar", new FeatureColour(Color.blue));
      ff.outputFeatureFilters(sb, visible, featureFilters);
 -    String expected = "\nSTARTFILTERS\nfoobar\tLabel Present\npfam\t(CSQ:PolyPhen Present) AND (Score LE -2.4)\nENDFILTERS\n\n";
 +    String expected = "\nSTARTFILTERS\nfoobar\tLabel Present\npfam\t(CSQ:PolyPhen Present) AND (Score LE -2.4)\nENDFILTERS\n";
      assertEquals(expected, sb.toString());
    }
 +
 +  /**
 +   * Output as GFF should not include features which are not visible due to
 +   * colour threshold or feature filter settings
 +   * 
 +   * @throws Exception
 +   */
 +  @Test(groups = { "Functional" })
 +  public void testPrintGffFormat_withFilters() throws Exception
 +  {
 +    File f = new File("examples/uniref50.fa");
 +    AlignmentI al = readAlignmentFile(f);
 +    AlignFrame af = new AlignFrame(al, 500, 500);
 +    SequenceFeature sf1 = new SequenceFeature("METAL", "Cath", 39, 39, 1.2f,
 +            null);
 +    sf1.setValue("clin_sig", "Likely Pathogenic");
 +    sf1.setValue("AF", "24");
 +    al.getSequenceAt(0).addSequenceFeature(sf1);
 +    SequenceFeature sf2 = new SequenceFeature("METAL", "Cath", 41, 41, 0.6f,
 +            null);
 +    sf2.setValue("clin_sig", "Benign");
 +    sf2.setValue("AF", "46");
 +    al.getSequenceAt(0).addSequenceFeature(sf2);
 +  
 +    FeaturesFile featuresFile = new FeaturesFile();
 +    FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
 +    final String gffHeader = "##gff-version 2\n";
 +
 +    fr.setVisible("METAL");
 +    fr.setColour("METAL", new FeatureColour(Color.PINK));
 +    String exported = featuresFile.printGffFormat(al.getSequencesArray(),
 +            fr, false);
 +    String expected = gffHeader
 +            + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
 +            + "FER_CAPAA\tCath\tMETAL\t41\t41\t0.6\t.\t.\n";
 +    assertEquals(expected, exported);
 +
 +    /*
 +     * now threshold to Score > 1.1 - should exclude sf2
 +     */
-     FeatureColourI fc = new FeatureColour(Color.white, Color.BLACK,
++    FeatureColourI fc = new FeatureColour(null, Color.white, Color.BLACK,
 +            Color.white, 0f, 2f);
 +    fc.setAboveThreshold(true);
 +    fc.setThreshold(1.1f);
 +    fr.setColour("METAL", fc);
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
 +            false);
 +    expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n";
 +    assertEquals(expected, exported);
 +
 +    /*
 +     * remove threshold and check sf2 is exported
 +     */
 +    fc.setAboveThreshold(false);
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
 +            false);
 +    expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
 +            + "FER_CAPAA\tCath\tMETAL\t41\t41\t0.6\t.\t.\n";
 +    assertEquals(expected, exported);
 +
 +    /*
 +     * filter on (clin_sig contains Benign) - should include sf2 and exclude sf1
 +     */
 +    FeatureMatcherSetI filter = new FeatureMatcherSet();
 +    filter.and(FeatureMatcher.byAttribute(Condition.Contains, "benign",
 +            "clin_sig"));
 +    fr.setFeatureFilter("METAL", filter);
 +    exported = featuresFile.printGffFormat(al.getSequencesArray(), fr,
 +            false);
 +    expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t41\t41\t0.6\t.\t.\n";
 +    assertEquals(expected, exported);
 +  }
 +
 +  /**
 +   * Output as Jalview should not include features which are not visible due to
 +   * colour threshold or feature filter settings
 +   * 
 +   * @throws Exception
 +   */
 +  @Test(groups = { "Functional" })
 +  public void testPrintJalviewFormat_withFilters() throws Exception
 +  {
 +    File f = new File("examples/uniref50.fa");
 +    AlignmentI al = readAlignmentFile(f);
 +    AlignFrame af = new AlignFrame(al, 500, 500);
 +    SequenceFeature sf1 = new SequenceFeature("METAL", "Cath", 39, 39, 1.2f,
 +            "grp1");
 +    sf1.setValue("clin_sig", "Likely Pathogenic");
 +    sf1.setValue("AF", "24");
 +    al.getSequenceAt(0).addSequenceFeature(sf1);
 +    SequenceFeature sf2 = new SequenceFeature("METAL", "Cath", 41, 41, 0.6f,
 +            "grp2");
 +    sf2.setValue("clin_sig", "Benign");
 +    sf2.setValue("AF", "46");
 +    al.getSequenceAt(0).addSequenceFeature(sf2);
 +  
 +    FeaturesFile featuresFile = new FeaturesFile();
 +    FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
 +    fr.findAllFeatures(true);
 +  
 +    fr.setVisible("METAL");
 +    fr.setColour("METAL", new FeatureColour(Color.PINK));
 +    String exported = featuresFile.printJalviewFormat(
 +            al.getSequencesArray(),
 +            fr, false);
 +    String expected = "METAL\tffafaf\n\nSTARTGROUP\tgrp1\n"
 +            + "Cath\tFER_CAPAA\t-1\t39\t39\tMETAL\t1.2\n"
 +            + "ENDGROUP\tgrp1\n\nSTARTGROUP\tgrp2\n"
 +            + "Cath\tFER_CAPAA\t-1\t41\t41\tMETAL\t0.6\n"
 +            + "ENDGROUP\tgrp2\n";
 +    assertEquals(expected, exported);
 +  
 +    /*
 +     * now threshold to Score > 1.1 - should exclude sf2
 +     * (and there should be no empty STARTGROUP/ENDGROUP output)
 +     */
-     FeatureColourI fc = new FeatureColour(Color.white, Color.BLACK,
++    FeatureColourI fc = new FeatureColour(null, Color.white, Color.BLACK,
 +            Color.white, 0f, 2f);
 +    fc.setAboveThreshold(true);
 +    fc.setThreshold(1.1f);
 +    fr.setColour("METAL", fc);
 +    exported = featuresFile.printJalviewFormat(al.getSequencesArray(), fr,
 +            false);
 +    expected = "METAL\tscore|ffffff|000000|noValueMin|abso|0.0|2.0|above|1.1\n\n"
 +            + "STARTGROUP\tgrp1\n"
 +            + "Cath\tFER_CAPAA\t-1\t39\t39\tMETAL\t1.2\n"
 +            + "ENDGROUP\tgrp1\n";
 +    assertEquals(expected, exported);
 +  
 +    /*
 +     * remove threshold and check sf2 is exported
 +     */
 +    fc.setAboveThreshold(false);
 +    exported = featuresFile.printJalviewFormat(al.getSequencesArray(), fr,
 +            false);
 +    expected = "METAL\tscore|ffffff|000000|noValueMin|abso|0.0|2.0|none\n\n"
 +            + "STARTGROUP\tgrp1\n"
 +            + "Cath\tFER_CAPAA\t-1\t39\t39\tMETAL\t1.2\n"
 +            + "ENDGROUP\tgrp1\n\nSTARTGROUP\tgrp2\n"
 +            + "Cath\tFER_CAPAA\t-1\t41\t41\tMETAL\t0.6\n"
 +            + "ENDGROUP\tgrp2\n";
 +    assertEquals(expected, exported);
 +  
 +    /*
 +     * filter on (clin_sig contains Benign) - should include sf2 and exclude sf1
 +     */
 +    FeatureMatcherSetI filter = new FeatureMatcherSet();
 +    filter.and(FeatureMatcher.byAttribute(Condition.Contains, "benign",
 +            "clin_sig"));
 +    fr.setFeatureFilter("METAL", filter);
 +    exported = featuresFile.printJalviewFormat(al.getSequencesArray(), fr,
 +            false);
 +    expected = "FER_CAPAA\tCath\tMETAL\t41\t41\t0.6\t.\t.\n";
 +    expected = "METAL\tscore|ffffff|000000|noValueMin|abso|0.0|2.0|none\n\n"
 +            + "STARTFILTERS\nMETAL\tclin_sig Contains benign\nENDFILTERS\n\n"
 +            + "STARTGROUP\tgrp2\n"
 +            + "Cath\tFER_CAPAA\t-1\t41\t41\tMETAL\t0.6\n"
 +            + "ENDGROUP\tgrp2\n";
 +    assertEquals(expected, exported);
 +  }
  }
@@@ -18,7 -18,7 +18,7 @@@
   * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
   * The Jalview Authors are detailed in the 'AUTHORS' file.
   */
 -package jalview.io;
 +package jalview.project;
  
  import static org.testng.Assert.assertEquals;
  import static org.testng.Assert.assertFalse;
@@@ -27,7 -27,6 +27,7 @@@ import static org.testng.Assert.assertN
  import static org.testng.Assert.assertSame;
  import static org.testng.Assert.assertTrue;
  
 +import jalview.analysis.scoremodels.SimilarityParams;
  import jalview.api.AlignViewportI;
  import jalview.api.AlignmentViewPanel;
  import jalview.api.FeatureColourI;
@@@ -49,14 -48,10 +49,14 @@@ import jalview.gui.AlignViewport
  import jalview.gui.AlignmentPanel;
  import jalview.gui.Desktop;
  import jalview.gui.FeatureRenderer;
 -import jalview.gui.Jalview2XML;
  import jalview.gui.JvOptionPane;
 +import jalview.gui.PCAPanel;
  import jalview.gui.PopupMenu;
  import jalview.gui.SliderPanel;
 +import jalview.io.DataSourceType;
 +import jalview.io.FileFormat;
 +import jalview.io.FileLoader;
 +import jalview.io.Jalview2xmlBase;
  import jalview.renderer.ResidueShaderI;
  import jalview.schemes.AnnotationColourGradient;
  import jalview.schemes.BuriedColourScheme;
@@@ -79,8 -74,6 +79,8 @@@ import java.util.HashMap
  import java.util.List;
  import java.util.Map;
  
 +import javax.swing.JInternalFrame;
 +
  import org.testng.Assert;
  import org.testng.AssertJUnit;
  import org.testng.annotations.BeforeClass;
@@@ -109,38 -102,35 +109,38 @@@ public class Jalview2xmlTests extends J
      assertNotNull(af, "Didn't read input file " + inFile);
      int olddsann = countDsAnn(af.getViewport());
      assertTrue(olddsann > 0, "Didn't find any dataset annotations");
 -    af.changeColour_actionPerformed(JalviewColourScheme.RNAHelices
 -            .toString());
 +    af.changeColour_actionPerformed(
 +            JalviewColourScheme.RNAHelices.toString());
      assertTrue(
 -            af.getViewport().getGlobalColourScheme() instanceof RNAHelicesColour,
 +            af.getViewport()
 +                    .getGlobalColourScheme() instanceof RNAHelicesColour,
              "Couldn't apply RNA helices colourscheme");
      assertTrue(af.saveAlignment(tfile, FileFormat.Jalview),
              "Failed to store as a project.");
      af.closeMenuItem_actionPerformed(true);
      af = null;
 -    af = new FileLoader()
 -            .LoadFileWaitTillLoaded(tfile, DataSourceType.FILE);
 +    af = new FileLoader().LoadFileWaitTillLoaded(tfile,
 +            DataSourceType.FILE);
      assertNotNull(af, "Failed to import new project");
      int newdsann = countDsAnn(af.getViewport());
      assertEquals(olddsann, newdsann,
              "Differing numbers of dataset sequence annotation\nOriginally "
                      + olddsann + " and now " + newdsann);
 -    System.out
 -            .println("Read in same number of annotations as originally present ("
 +    System.out.println(
 +            "Read in same number of annotations as originally present ("
                      + olddsann + ")");
      assertTrue(
  
 -    af.getViewport().getGlobalColourScheme() instanceof RNAHelicesColour,
 +            af.getViewport()
 +                    .getGlobalColourScheme() instanceof RNAHelicesColour,
              "RNA helices colourscheme was not applied on import.");
    }
  
    @Test(groups = { "Functional" })
    public void testTCoffeeScores() throws Exception
    {
 -    String inFile = "examples/uniref50.fa", inAnnot = "examples/uniref50.score_ascii";
 +    String inFile = "examples/uniref50.fa",
 +            inAnnot = "examples/uniref50.score_ascii";
      String tfile = File.createTempFile("JalviewTest", ".jvp")
              .getAbsolutePath();
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
      af.loadJalviewDataFile(inAnnot, DataSourceType.FILE, null, null);
      assertSame(af.getViewport().getGlobalColourScheme().getClass(),
              TCoffeeColourScheme.class, "Didn't set T-coffee colourscheme");
 -    assertNotNull(ColourSchemeProperty.getColourScheme(af.getViewport()
 -            .getAlignment(), af.getViewport().getGlobalColourScheme()
 -            .getSchemeName()), "Recognise T-Coffee score from string");
 +    assertNotNull(
 +            ColourSchemeProperty.getColourScheme(
 +                    af.getViewport().getAlignment(),
 +                    af.getViewport().getGlobalColourScheme()
 +                            .getSchemeName()),
 +            "Recognise T-Coffee score from string");
  
      assertTrue(af.saveAlignment(tfile, FileFormat.Jalview),
              "Failed to store as a project.");
      af.closeMenuItem_actionPerformed(true);
      af = null;
 -    af = new FileLoader()
 -            .LoadFileWaitTillLoaded(tfile, DataSourceType.FILE);
 +    af = new FileLoader().LoadFileWaitTillLoaded(tfile,
 +            DataSourceType.FILE);
      assertNotNull(af, "Failed to import new project");
      assertSame(af.getViewport().getGlobalColourScheme().getClass(),
              TCoffeeColourScheme.class,
              "Didn't set T-coffee colourscheme for imported project.");
 -    System.out
 -            .println("T-Coffee score shading successfully recovered from project.");
 +    System.out.println(
 +            "T-Coffee score shading successfully recovered from project.");
    }
  
    @Test(groups = { "Functional" })
    public void testColourByAnnotScores() throws Exception
    {
 -    String inFile = "examples/uniref50.fa", inAnnot = "examples/testdata/uniref50_iupred.jva";
 +    String inFile = "examples/uniref50.fa",
 +            inAnnot = "examples/testdata/uniref50_iupred.jva";
      String tfile = File.createTempFile("JalviewTest", ".jvp")
              .getAbsolutePath();
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
              .getSequenceAt(0).getAnnotation("IUPredWS (Short)");
      assertTrue(
  
 -    aa != null && aa.length > 0,
 +            aa != null && aa.length > 0,
              "Didn't find any IUPred annotation to use to shade alignment.");
      AnnotationColourGradient cs = new AnnotationColourGradient(aa[0], null,
              AnnotationColourGradient.ABOVE_THRESHOLD);
 -    AnnotationColourGradient gcs = new AnnotationColourGradient(aa[0],
 -            null, AnnotationColourGradient.BELOW_THRESHOLD);
 +    AnnotationColourGradient gcs = new AnnotationColourGradient(aa[0], null,
 +            AnnotationColourGradient.BELOW_THRESHOLD);
      cs.setSeqAssociated(true);
      gcs.setSeqAssociated(true);
      af.changeColour(cs);
              "Failed to store as a project.");
      af.closeMenuItem_actionPerformed(true);
      af = null;
 -    af = new FileLoader()
 -            .LoadFileWaitTillLoaded(tfile, DataSourceType.FILE);
 +    af = new FileLoader().LoadFileWaitTillLoaded(tfile,
 +            DataSourceType.FILE);
      assertNotNull(af, "Failed to import new project");
  
      // check for group and alignment colourschemes
  
      ColourSchemeI _rcs = af.getViewport().getGlobalColourScheme();
 -    ColourSchemeI _rgcs = af.getViewport().getAlignment().getGroups()
 -            .get(0).getColourScheme();
 +    ColourSchemeI _rgcs = af.getViewport().getAlignment().getGroups().get(0)
 +            .getColourScheme();
      assertNotNull(_rcs, "Didn't recover global colourscheme");
      assertTrue(_rcs instanceof AnnotationColourGradient,
              "Didn't recover annotation colour global scheme");
  
      boolean diffseqcols = false, diffgseqcols = false;
      SequenceI[] sqs = af.getViewport().getAlignment().getSequencesArray();
 -    for (int p = 0, pSize = af.getViewport().getAlignment().getWidth(); p < pSize
 -            && (!diffseqcols || !diffgseqcols); p++)
 +    for (int p = 0, pSize = af.getViewport().getAlignment()
 +            .getWidth(); p < pSize && (!diffseqcols || !diffgseqcols); p++)
      {
        if (_rcs.findColour(sqs[0].getCharAt(p), p, sqs[0], null, 0f) != _rcs
                .findColour(sqs[5].getCharAt(p), p, sqs[5], null, 0f))
        }
      }
      assertTrue(diffseqcols, "Got Different sequence colours");
 -    System.out
 -            .println("Per sequence colourscheme (Background) successfully applied and recovered.");
 +    System.out.println(
 +            "Per sequence colourscheme (Background) successfully applied and recovered.");
  
      assertNotNull(_rgcs, "Didn't recover group colourscheme");
      assertTrue(_rgcs instanceof AnnotationColourGradient,
      assertTrue(__rcs.isSeqAssociated(),
              "Group Annotation colourscheme wasn't sequence associated");
  
 -    for (int p = 0, pSize = af.getViewport().getAlignment().getWidth(); p < pSize
 -            && (!diffseqcols || !diffgseqcols); p++)
 +    for (int p = 0, pSize = af.getViewport().getAlignment()
 +            .getWidth(); p < pSize && (!diffseqcols || !diffgseqcols); p++)
      {
 -      if (_rgcs.findColour(sqs[1].getCharAt(p), p, sqs[1], null, 0f) != _rgcs
 -              .findColour(sqs[2].getCharAt(p), p, sqs[2], null, 0f))
 +      if (_rgcs.findColour(sqs[1].getCharAt(p), p, sqs[1], null,
 +              0f) != _rgcs.findColour(sqs[2].getCharAt(p), p, sqs[2], null,
 +                      0f))
        {
          diffgseqcols = true;
        }
      }
      assertTrue(diffgseqcols, "Got Different group sequence colours");
 -    System.out
 -            .println("Per sequence (Group) colourscheme successfully applied and recovered.");
 +    System.out.println(
 +            "Per sequence (Group) colourscheme successfully applied and recovered.");
    }
  
    @Test(groups = { "Functional" })
    public void gatherViewsHere() throws Exception
    {
 -    int origCount = Desktop.getAlignFrames() == null ? 0 : Desktop
 -            .getAlignFrames().length;
 +    int origCount = Desktop.getAlignFrames() == null ? 0
 +            : Desktop.getAlignFrames().length;
      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
              "examples/exampleFile_2_7.jar", DataSourceType.FILE);
      assertNotNull(af, "Didn't read in the example file correctly.");
            sq.findPosition(p);
            try
            {
 -            assertTrue(
 -                    (alaa.annotations[p] == null && refan.annotations[p] == null)
 -                            || alaa.annotations[p].value == refan.annotations[p].value,
 +            assertTrue((alaa.annotations[p] == null
 +                    && refan.annotations[p] == null)
 +                    || alaa.annotations[p].value == refan.annotations[p].value,
                      "Mismatch at alignment position " + p);
            } catch (NullPointerException q)
            {
 -            Assert.fail("Mismatch of alignment annotations at position "
 -                    + p + " Ref seq ann: " + refan.annotations[p]
 +            Assert.fail("Mismatch of alignment annotations at position " + p
 +                    + " Ref seq ann: " + refan.annotations[p]
                      + " alignment " + alaa.annotations[p]);
            }
          }
      AssertJUnit.assertFalse(structureStyle.sameStyle(groupStyle));
  
      groups.getAlignViewport().setViewStyle(structureStyle);
 -    AssertJUnit.assertFalse(groupStyle.sameStyle(groups.getAlignViewport()
 -            .getViewStyle()));
 -    Assert.assertTrue(structureStyle.sameStyle(groups.getAlignViewport()
 -            .getViewStyle()));
 +    AssertJUnit.assertFalse(
 +            groupStyle.sameStyle(groups.getAlignViewport().getViewStyle()));
 +    Assert.assertTrue(structureStyle
 +            .sameStyle(groups.getAlignViewport().getViewStyle()));
  
    }
  
  
      // check FileLoader returned a reference to the one alignFrame that is
      // actually on the Desktop
 -    assertSame(
 -            af,
 -            Desktop.getAlignFrameFor(af.getViewport()),
 +    assertSame(af, Desktop.getAlignFrameFor(af.getViewport()),
              "Jalview2XML.loadAlignFrame() didn't return correct AlignFrame reference for multiple view window");
  
      Desktop.explodeViews(af);
      af = new FileLoader().LoadFileWaitTillLoaded(tfile.getAbsolutePath(),
              DataSourceType.FILE);
      Assert.assertNotNull(af);
 +    Assert.assertEquals(Desktop.getAlignFrames().length,
 +            Desktop.getAlignmentPanels(
 +                    af.getViewport().getSequenceSetId()).length);
      Assert.assertEquals(
 -            Desktop.getAlignFrames().length,
 -            Desktop.getAlignmentPanels(af.getViewport().getSequenceSetId()).length);
 -    Assert.assertEquals(
 -            Desktop.getAlignmentPanels(af.getViewport().getSequenceSetId()).length,
 +            Desktop.getAlignmentPanels(
 +                    af.getViewport().getSequenceSetId()).length,
              oldviews);
    }
  
      assertTrue(Jalview2XML.isVersionStringLaterThan(null, "Test"));
      assertTrue(Jalview2XML.isVersionStringLaterThan(null, "TEST"));
      assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3", "Test"));
 -    assertTrue(Jalview2XML
 -            .isVersionStringLaterThan(null, "Automated Build"));
 +    assertTrue(
 +            Jalview2XML.isVersionStringLaterThan(null, "Automated Build"));
      assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3",
              "Automated Build"));
      assertTrue(Jalview2XML.isVersionStringLaterThan("2.8.3",
  
        n++;
      }
 -    File tfile = File
 -            .createTempFile("testStoreAndRecoverGroupReps", ".jvp");
 +    File tfile = File.createTempFile("testStoreAndRecoverGroupReps",
 +            ".jvp");
      try
      {
        new Jalview2XML(false).saveState(tfile);
         */
        List<String> hidden = hiddenSeqNames.get(ap.getViewName());
        HiddenSequences hs = alignment.getHiddenSequences();
 -      assertEquals(
 -              hidden.size(),
 -              hs.getSize(),
 +      assertEquals(hidden.size(), hs.getSize(),
                "wrong number of restored hidden sequences in "
                        + ap.getViewName());
      }
      pdbEntries[1] = new PDBEntry("3W5V", "B", Type.PDB, testFile);
      pdbEntries[2] = new PDBEntry("3W5V", "C", Type.PDB, testFile);
      pdbEntries[3] = new PDBEntry("3W5V", "D", Type.PDB, testFile);
 -    Assert.assertEquals(seqs[0].getDatasetSequence().getAllPDBEntries()
 -            .get(0), pdbEntries[0]);
 -    Assert.assertEquals(seqs[1].getDatasetSequence().getAllPDBEntries()
 -            .get(0), pdbEntries[1]);
 -    Assert.assertEquals(seqs[2].getDatasetSequence().getAllPDBEntries()
 -            .get(0), pdbEntries[2]);
 -    Assert.assertEquals(seqs[3].getDatasetSequence().getAllPDBEntries()
 -            .get(0), pdbEntries[3]);
 +    Assert.assertEquals(
 +            seqs[0].getDatasetSequence().getAllPDBEntries().get(0),
 +            pdbEntries[0]);
 +    Assert.assertEquals(
 +            seqs[1].getDatasetSequence().getAllPDBEntries().get(0),
 +            pdbEntries[1]);
 +    Assert.assertEquals(
 +            seqs[2].getDatasetSequence().getAllPDBEntries().get(0),
 +            pdbEntries[2]);
 +    Assert.assertEquals(
 +            seqs[3].getDatasetSequence().getAllPDBEntries().get(0),
 +            pdbEntries[3]);
  
      File tfile = File.createTempFile("testStoreAndRecoverPDBEntry", ".jvp");
      try
      sg.setEndRes(25);
      av.setSelectionGroup(sg);
      PopupMenu popupMenu = new PopupMenu(af.alignPanel, null, null);
 -    popupMenu.changeColour_actionPerformed(JalviewColourScheme.Strand
 -            .toString());
 +    popupMenu.changeColour_actionPerformed(
 +            JalviewColourScheme.Strand.toString());
      assertTrue(sg.getColourScheme() instanceof StrandColourScheme);
      assertEquals(al.getGroups().size(), 1);
      assertSame(al.getGroups().get(0), sg);
      fr.setColour("type2", byLabel);
  
      // type3: by score above threshold
-     FeatureColourI byScore = new FeatureColour(Color.BLACK, Color.BLUE, 1,
-             10);
+     FeatureColourI byScore = new FeatureColour(null, Color.BLACK,
+             Color.BLUE, null, 1, 10);
      byScore.setAboveThreshold(true);
      byScore.setThreshold(2f);
      fr.setColour("type3", byScore);
      fr.setColour("type4", byAF);
  
      // type5: by attribute CSQ:PolyPhen below threshold
-     FeatureColourI byPolyPhen = new FeatureColour(Color.BLACK, Color.BLUE,
-             1, 10);
+     FeatureColourI byPolyPhen = new FeatureColour(null, Color.BLACK,
+             Color.BLUE, null, 1, 10);
      byPolyPhen.setBelowThreshold(true);
      byPolyPhen.setThreshold(3f);
      byPolyPhen.setAttributeName("CSQ", "PolyPhen");
       */
      af.closeMenuItem_actionPerformed(true);
      af = null;
 -    af = new FileLoader()
 -            .LoadFileWaitTillLoaded(filePath, DataSourceType.FILE);
 +    af = new FileLoader().LoadFileWaitTillLoaded(filePath,
 +            DataSourceType.FILE);
      assertNotNull(af, "Failed to import new project");
  
      /*
      addFeature(seq, featureType, score++);
      addFeature(seq, featureType, score);
    }
 +
 +  /**
 +   * pre 2.11 - jalview 2.10 erroneously created new dataset entries for each
 +   * view (JAL-3171) this test ensures we can import and merge those views
 +   */
 +  @Test(groups = { "Functional" })
 +  public void testMergeDatasetsforViews() throws IOException
 +  {
 +    // simple project - two views on one alignment
 +    AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded(
 +            "examples/testdata/projects/twoViews.jvp", DataSourceType.FILE);
 +    assertNotNull(af);
 +    assertTrue(af.getAlignPanels().size() > 1);
 +    verifyDs(af);
 +  }
 +
 +  /**
 +   * pre 2.11 - jalview 2.10 erroneously created new dataset entries for each
 +   * view (JAL-3171) this test ensures we can import and merge those views This
 +   * is a more complex project
 +   */
 +  @Test(groups = { "Functional" })
 +  public void testMergeDatasetsforManyViews() throws IOException
 +  {
 +    Desktop.instance.closeAll_actionPerformed(null);
 +
 +    // complex project - one dataset, several views on several alignments
 +    AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded(
 +            "examples/testdata/projects/manyViews.jvp",
 +            DataSourceType.FILE);
 +    assertNotNull(af);
 +
 +    AlignmentI ds = null;
 +    for (AlignFrame alignFrame : Desktop.getAlignFrames())
 +    {
 +      if (ds == null)
 +      {
 +        ds = verifyDs(alignFrame);
 +      }
 +      else
 +      {
 +        // check that this frame's dataset matches the last
 +        assertTrue(ds == verifyDs(alignFrame));
 +      }
 +    }
 +  }
 +
 +  private AlignmentI verifyDs(AlignFrame af)
 +  {
 +    AlignmentI ds = null;
 +    for (AlignmentViewPanel ap : af.getAlignPanels())
 +    {
 +      if (ds == null)
 +      {
 +        ds = ap.getAlignment().getDataset();
 +      }
 +      else
 +      {
 +        assertTrue(ap.getAlignment().getDataset() == ds,
 +                "Dataset was not the same for imported 2.10.5 project with several alignment views");
 +      }
 +    }
 +    return ds;
 +  }
 +
 +  @Test(groups = "Functional")
 +  public void testPcaViewAssociation() throws IOException
 +  {
 +    Desktop.instance.closeAll_actionPerformed(null);
 +    final String PCAVIEWNAME = "With PCA";
 +    // create a new tempfile
 +    File tempfile = File.createTempFile("jvPCAviewAssoc", "jvp");
 +
 +    {
 +      String exampleFile = "examples/uniref50.fa";
 +      AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(exampleFile,
 +              DataSourceType.FILE);
 +      assertNotNull(af, "Didn't read in the example file correctly.");
 +      AlignmentPanel origView = (AlignmentPanel) af.getAlignPanels().get(0);
 +      AlignmentPanel newview = af.newView(PCAVIEWNAME, true);
 +      // create another for good measure
 +      af.newView("Not the PCA View", true);
 +      PCAPanel pcaPanel = new PCAPanel(origView, "BLOSUM62",
 +              new SimilarityParams(true, true, true, false));
 +      // we're in the test exec thread, so we can just run synchronously here
 +      pcaPanel.run();
 +
 +      // now switch the linked view
 +      pcaPanel.selectAssociatedView(newview);
 +
 +      assertTrue(pcaPanel.getAlignViewport() == newview.getAlignViewport(),
 +              "PCA should be associated with 'With PCA' view: test is broken");
 +
 +      // now save and reload project
 +      Jalview2XML jv2xml = new jalview.project.Jalview2XML(false);
 +      tempfile.delete();
 +      jv2xml.saveState(tempfile);
 +      assertTrue(jv2xml.errorMessage == null,
 +              "Failed to save dummy project with PCA: test broken");
 +    }
 +
 +    // load again.
 +    Desktop.instance.closeAll_actionPerformed(null);
 +    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
 +            tempfile.getCanonicalPath(), DataSourceType.FILE);
 +    JInternalFrame[] frames = Desktop.instance.getAllFrames();
 +    // PCA and the tabbed alignment view should be the only two windows on the
 +    // desktop
 +    assertEquals(frames.length, 2,
 +            "PCA and the tabbed alignment view should be the only two windows on the desktop");
 +    PCAPanel pcaPanel = (PCAPanel) frames[frames[0] == af ? 1 : 0];
 +
 +    AlignmentViewPanel restoredNewView = null;
 +    for (AlignmentViewPanel alignpanel : Desktop.getAlignmentPanels(null))
 +    {
 +      if (alignpanel.getAlignViewport() == pcaPanel.getAlignViewport())
 +      {
 +        restoredNewView = alignpanel;
 +      }
 +    }
 +    assertEquals(restoredNewView.getViewName(), PCAVIEWNAME);
 +    assertTrue(
 +            restoredNewView.getAlignViewport() == pcaPanel
 +                    .getAlignViewport(),
 +            "Didn't restore correct view association for the PCA view");
 +  }
  }
@@@ -285,8 -285,8 +285,8 @@@ public class FeatureRendererTes
       * give "Type3" features a graduated colour scheme
       * - first with no threshold
       */
-     FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, null, 0f,
-             10f);
+     FeatureColourI gc = new FeatureColour(Color.green, Color.yellow,
+             Color.red, null, 0f, 10f);
      fr.getFeatureColours().put("Type3", gc);
      features = fr.findFeaturesAtColumn(seq, 8);
      assertTrue(features.contains(sf4));
      SequenceI seq = av.getAlignment().getSequenceAt(0);
      SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
              "group1");
 -    seq.addSequenceFeature(sf1);
      SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
              "group2");
 -    seq.addSequenceFeature(sf2);
      SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
              "group3");
 -    seq.addSequenceFeature(sf3);
      SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
              "group4");
 -    seq.addSequenceFeature(sf4);
      SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
              "group4");
 +    seq.addSequenceFeature(sf1);
 +    seq.addSequenceFeature(sf2);
 +    seq.addSequenceFeature(sf3);
 +    seq.addSequenceFeature(sf4);
      seq.addSequenceFeature(sf5);
  
      fr.findAllFeatures(true);
      assertTrue(features.contains(sf5));
  
      /*
 -     * hide groups 2 and 3 makes no difference to this method
 +     * features in hidden groups are removed
       */
      fr.setGroupVisibility("group2", false);
      fr.setGroupVisibility("group3", false);
      features = seq.getSequenceFeatures();
      fr.filterFeaturesForDisplay(features);
 -    assertEquals(features.size(), 3);
 +    assertEquals(features.size(), 2);
      assertTrue(features.contains(sf1) || features.contains(sf4));
      assertFalse(features.contains(sf1) && features.contains(sf4));
 -    assertTrue(features.contains(sf2) || features.contains(sf3));
 -    assertFalse(features.contains(sf2) && features.contains(sf3));
 +    assertFalse(features.contains(sf2));
 +    assertFalse(features.contains(sf3));
      assertTrue(features.contains(sf5));
  
      /*
       * graduated colour by score, no threshold, no score
       * 
       */
-     FeatureColourI gc = new FeatureColour(Color.yellow, Color.red,
-             Color.green, 1f, 11f);
+     FeatureColourI gc = new FeatureColour(Color.red, Color.yellow,
+             Color.red, Color.green, 1f, 11f);
      fr.getFeatureColours().put("Cath", gc);
      assertEquals(fr.getColour(sf1), Color.green);
  
       * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11
       * or from yellow(255, 255, 0) to red(255, 0, 0)
       */
-     gc = new FeatureColour(Color.yellow, Color.red, Color.green, 5f, 11f);
+     gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green,
+             5f, 11f);
      fr.getFeatureColours().put("Cath", gc);
      gc.setAutoScaled(false); // this does little other than save a checkbox setting!
      assertEquals(fr.getColour(sf2), new Color(255, 213, 0));
       * colour by feature attribute value
       * first with no value held
       */
-     gc = new FeatureColour(Color.yellow, Color.red, Color.green, 1f, 11f);
+     gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green,
+             1f, 11f);
      fr.getFeatureColours().put("Cath", gc);
      gc.setAttributeName("AF");
      assertEquals(fr.getColour(sf2), Color.green);
      csqData.put("Feature", "ENST01234");
      assertEquals(fr.getColour(sf2), expected);
    }
 +
 +  @Test(groups = "Functional")
 +  public void testIsVisible()
 +  {
 +    String seqData = ">s1\nMLQGIFPRS\n";
 +    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
 +            DataSourceType.PASTE);
 +    AlignViewportI av = af.getViewport();
 +    FeatureRenderer fr = new FeatureRenderer(av);
 +    SequenceI seq = av.getAlignment().getSequenceAt(0);
 +    SequenceFeature sf = new SequenceFeature("METAL", "Desc", 10, 10, 1f,
 +            "Group");
 +    sf.setValue("AC", "11");
 +    sf.setValue("CLIN_SIG", "Likely Pathogenic");
 +    seq.addSequenceFeature(sf);
 +
 +    assertFalse(fr.isVisible(null));
 +
 +    /*
 +     * initial state FeatureRenderer hasn't 'found' feature
 +     * and so its feature type has not yet been set visible
 +     */
 +    assertFalse(fr.getDisplayedFeatureCols().containsKey("METAL"));
 +    assertFalse(fr.isVisible(sf));
 +
 +    fr.findAllFeatures(true);
 +    assertTrue(fr.isVisible(sf));
 +
 +    /*
 +     * feature group not visible
 +     */
 +    fr.setGroupVisibility("Group", false);
 +    assertFalse(fr.isVisible(sf));
 +    fr.setGroupVisibility("Group", true);
 +    assertTrue(fr.isVisible(sf));
 +
 +    /*
 +     * feature score outwith colour threshold (score > 2)
 +     */
-     FeatureColourI fc = new FeatureColour(Color.white, Color.black,
++    FeatureColourI fc = new FeatureColour(null, Color.white, Color.black,
 +            Color.white, 0, 10);
 +    fc.setAboveThreshold(true);
 +    fc.setThreshold(2f);
 +    fr.setColour("METAL", fc);
 +    assertFalse(fr.isVisible(sf)); // score 1 is not above threshold 2
 +    fc.setBelowThreshold(true);
 +    assertTrue(fr.isVisible(sf)); // score 1 is below threshold 2
 +
 +    /*
 +     * colour with threshold on attribute AC (value is 11)
 +     */
 +    fc.setAttributeName("AC");
 +    assertFalse(fr.isVisible(sf)); // value 11 is not below threshold 2
 +    fc.setAboveThreshold(true);
 +    assertTrue(fr.isVisible(sf)); // value 11 is above threshold 2
 +
 +    fc.setAttributeName("AF"); // attribute AF is absent in sf
 +    assertTrue(fr.isVisible(sf)); // feature is not excluded by threshold
 +
 +    FeatureMatcherSetI filter = new FeatureMatcherSet();
 +    filter.and(FeatureMatcher.byAttribute(Condition.Contains, "pathogenic",
 +            "CLIN_SIG"));
 +    fr.setFeatureFilter("METAL", filter);
 +    assertTrue(fr.isVisible(sf)); // feature matches filter
 +    filter.and(FeatureMatcher.byScore(Condition.LE, "0.4"));
 +    assertFalse(fr.isVisible(sf)); // feature doesn't match filter
 +  }
  }
@@@ -38,8 -38,6 +38,6 @@@ import java.awt.Color
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.Test;
  
- import junit.extensions.PA;
  public class FeatureColourTest
  {
  
    }
  
    @Test(groups = { "Functional" })
+   public void testConstructors()
+   {
+     FeatureColourI fc = new FeatureColour();
+     assertNull(fc.getColour());
+     assertTrue(fc.isSimpleColour());
+     assertFalse(fc.isColourByLabel());
+     assertFalse(fc.isGraduatedColour());
+     assertFalse(fc.isColourByAttribute());
+     assertEquals(Color.white, fc.getMinColour());
+     assertEquals(Color.black, fc.getMaxColour());
+     fc = new FeatureColour(Color.RED);
+     assertEquals(Color.red, fc.getColour());
+     assertTrue(fc.isSimpleColour());
+     assertFalse(fc.isColourByLabel());
+     assertFalse(fc.isGraduatedColour());
+     assertFalse(fc.isColourByAttribute());
+     assertEquals(ColorUtils.bleachColour(Color.RED, 0.9f),
+             fc.getMinColour());
+     assertEquals(Color.RED, fc.getMaxColour());
+   }
+   @Test(groups = { "Functional" })
    public void testCopyConstructor()
    {
      /*
@@@ -67,7 -89,8 +89,8 @@@
      /*
       * min-max colour
       */
-     fc = new FeatureColour(Color.gray, Color.black, 10f, 20f);
+     fc = new FeatureColour(null, Color.gray, Color.black, Color.gray, 10f,
+             20f);
      fc.setAboveThreshold(true);
      fc.setThreshold(12f);
      fc1 = new FeatureColour(fc);
      /*
       * min-max-noValue colour
       */
-     fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
+     fc = new FeatureColour(Color.red, Color.gray, Color.black, Color.green,
+             10f, 20f);
      fc.setAboveThreshold(true);
      fc.setThreshold(12f);
      fc1 = new FeatureColour(fc);
      assertTrue(fc1.isGraduatedColour());
      assertFalse(fc1.isColourByLabel());
+     assertFalse(fc1.isSimpleColour());
      assertFalse(fc1.isColourByAttribute());
      assertNull(fc1.getAttributeName());
      assertTrue(fc1.isAboveThreshold());
      assertEquals(Color.gray, fc1.getMinColour());
      assertEquals(Color.black, fc1.getMaxColour());
      assertEquals(Color.green, fc1.getNoColour());
+     assertEquals(Color.red, fc1.getColour());
      assertEquals(10f, fc1.getMin());
      assertEquals(20f, fc1.getMax());
  
      /*
       * colour by attribute (value)
       */
-     fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
+     fc = new FeatureColour(Color.yellow, Color.gray, Color.black,
+             Color.green, 10f, 20f);
      fc.setAboveThreshold(true);
      fc.setThreshold(12f);
      fc.setAttributeName("AF");
      assertTrue(fc1.isGraduatedColour());
      assertFalse(fc1.isColourByLabel());
      assertTrue(fc1.isColourByAttribute());
+     assertFalse(fc1.isSimpleColour());
      assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName());
      assertTrue(fc1.isAboveThreshold());
      assertEquals(12f, fc1.getThreshold());
      assertEquals(Color.gray, fc1.getMinColour());
      assertEquals(Color.black, fc1.getMaxColour());
      assertEquals(Color.green, fc1.getNoColour());
+     assertEquals(Color.yellow, fc1.getColour());
      assertEquals(10f, fc1.getMin());
      assertEquals(20f, fc1.getMax());
    }
  
    @Test(groups = { "Functional" })
-   public void testCopyConstructor_minMax()
-   {
-     /*
-      * graduated colour
-      */
-     FeatureColour fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
-     assertTrue(fc.isGraduatedColour());
-     assertFalse(fc.isColourByLabel());
-     assertFalse(fc.isColourByAttribute());
-     assertNull(fc.getAttributeName());
-     assertEquals(1f, fc.getMin());
-     assertEquals(5f, fc.getMax());
-     /*
-      * update min-max bounds
-      */
-     FeatureColour fc1 = new FeatureColour(fc, 2f, 6f);
-     assertTrue(fc1.isGraduatedColour());
-     assertFalse(fc1.isColourByLabel());
-     assertFalse(fc1.isColourByAttribute());
-     assertNull(fc1.getAttributeName());
-     assertEquals(2f, fc1.getMin());
-     assertEquals(6f, fc1.getMax());
-     assertFalse((boolean) PA.getValue(fc1, "isHighToLow"));
-     /*
-      * update min-max bounds - high to low
-      */
-     fc1 = new FeatureColour(fc, 23f, 16f);
-     assertTrue(fc1.isGraduatedColour());
-     assertFalse(fc1.isColourByLabel());
-     assertFalse(fc1.isColourByAttribute());
-     assertNull(fc1.getAttributeName());
-     assertEquals(23f, fc1.getMin());
-     assertEquals(16f, fc1.getMax());
-     assertTrue((boolean) PA.getValue(fc1, "isHighToLow"));
-     /*
-      * graduated colour by attribute
-      */
-     fc1.setAttributeName("AF");
-     fc1 = new FeatureColour(fc1, 13f, 36f);
-     assertTrue(fc1.isGraduatedColour());
-     assertFalse(fc1.isColourByLabel());
-     assertTrue(fc1.isColourByAttribute());
-     assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName());
-     assertEquals(13f, fc1.getMin());
-     assertEquals(36f, fc1.getMax());
-     assertFalse((boolean) PA.getValue(fc1, "isHighToLow"));
-     /*
-      * colour by label
-      */
-     fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
-     fc.setColourByLabel(true);
-     assertFalse(fc.isGraduatedColour());
-     assertTrue(fc.isColourByLabel());
-     assertFalse(fc.isColourByAttribute());
-     assertNull(fc.getAttributeName());
-     assertEquals(1f, fc.getMin());
-     assertEquals(5f, fc.getMax());
-     /*
-      * update min-max bounds
-      */
-     fc1 = new FeatureColour(fc, 2f, 6f);
-     assertFalse(fc1.isGraduatedColour());
-     assertTrue(fc1.isColourByLabel());
-     assertFalse(fc1.isColourByAttribute());
-     assertNull(fc1.getAttributeName());
-     assertEquals(2f, fc1.getMin());
-     assertEquals(6f, fc1.getMax());
-     /*
-      * colour by attribute text
-      */
-     fc1.setAttributeName("AC");
-     fc1 = new FeatureColour(fc1, 13f, 36f);
-     assertFalse(fc1.isGraduatedColour());
-     assertTrue(fc1.isColourByLabel());
-     assertTrue(fc1.isColourByAttribute());
-     assertArrayEquals(new String[] { "AC" }, fc1.getAttributeName());
-     assertEquals(13f, fc1.getMin());
-     assertEquals(36f, fc1.getMax());
-   }
-   @Test(groups = { "Functional" })
    public void testGetColor_simpleColour()
    {
      FeatureColour fc = new FeatureColour(Color.RED);
       * score 0 to 100
       * gray(128, 128, 128) to red(255, 0, 0)
       */
-     FeatureColour fc = new FeatureColour(Color.GRAY, Color.RED, 0f, 100f);
+     FeatureColour fc = new FeatureColour(null, Color.GRAY, Color.RED, null,
+             0f, 100f);
      // feature score is 75 which is 3/4 of the way from GRAY to RED
      SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
              null);
    public void testGetColor_aboveBelowThreshold()
    {
      // gradient from [50, 150] from WHITE(255, 255, 255) to BLACK(0, 0, 0)
-     FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 50f,
-             150f);
+     FeatureColour fc = new FeatureColour(null, Color.WHITE, Color.BLACK,
+             Color.white, 50f, 150f);
      SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 70f,
              null);
  
       * graduated colour by score, no threshold
       * - default constructor sets noValueColor = minColor
       */
-     fc = new FeatureColour(Color.GREEN, Color.RED, 12f, 25f);
+     fc = new FeatureColour(null, Color.GREEN, Color.RED, Color.GREEN, 12f,
+             25f);
      String greenHex = Format.getHexString(Color.GREEN);
      String expected = String.format(
              "domain\tscore|%s|%s|noValueMin|abso|12.0|25.0|none", greenHex,
      /*
       * graduated colour by score, no threshold, no value gets min colour
       */
-     fc = new FeatureColour(Color.GREEN, Color.RED, Color.GREEN, 12f, 25f);
+     fc = new FeatureColour(Color.RED, Color.GREEN, Color.RED, Color.GREEN,
+             12f, 25f);
      expected = String.format(
              "domain\tscore|%s|%s|noValueMin|abso|12.0|25.0|none", greenHex,
              redHex);
      /*
       * graduated colour by score, no threshold, no value gets max colour
       */
-     fc = new FeatureColour(Color.GREEN, Color.RED, Color.RED, 12f, 25f);
+     fc = new FeatureColour(Color.RED, Color.GREEN, Color.RED, Color.RED,
+             12f, 25f);
      expected = String.format(
              "domain\tscore|%s|%s|noValueMax|abso|12.0|25.0|none", greenHex,
              redHex);
      assertFalse(fc.hasThreshold());
      assertEquals(Color.RED, fc.getMinColour());
      assertEquals(Color.GREEN, fc.getMaxColour());
 +    assertEquals(Color.RED, fc.getNoColour());
      assertEquals(10f, fc.getMin());
      assertEquals(20f, fc.getMax());
      assertTrue(fc.isAutoScaled());
  
      /*
 +     * the same, with 'no value colour' specified as max
 +     */
 +    fc = FeatureColour
 +            .parseJalviewFeatureColour("red|green|novaluemax|10.0|20.0");
 +    assertEquals(Color.RED, fc.getMinColour());
 +    assertEquals(Color.GREEN, fc.getMaxColour());
 +    assertEquals(Color.GREEN, fc.getNoColour());
 +    assertEquals(10f, fc.getMin());
 +    assertEquals(20f, fc.getMax());
 +
 +    /*
 +     * the same, with 'no value colour' specified as min
 +     */
 +    fc = FeatureColour
 +            .parseJalviewFeatureColour("red|green|novalueMin|10.0|20.0");
 +    assertEquals(Color.RED, fc.getMinColour());
 +    assertEquals(Color.GREEN, fc.getMaxColour());
 +    assertEquals(Color.RED, fc.getNoColour());
 +    assertEquals(10f, fc.getMin());
 +    assertEquals(20f, fc.getMax());
 +
 +    /*
 +     * the same, with 'no value colour' specified as none
 +     */
 +    fc = FeatureColour
 +            .parseJalviewFeatureColour("red|green|novaluenone|10.0|20.0");
 +    assertEquals(Color.RED, fc.getMinColour());
 +    assertEquals(Color.GREEN, fc.getMaxColour());
 +    assertNull(fc.getNoColour());
 +    assertEquals(10f, fc.getMin());
 +    assertEquals(20f, fc.getMax());
 +
 +    /*
 +     * the same, with invalid 'no value colour'
 +     */
 +    try
 +    {
 +      fc = FeatureColour
 +              .parseJalviewFeatureColour("red|green|blue|10.0|20.0");
 +      fail("expected exception");
 +    } catch (IllegalArgumentException e)
 +    {
 +      assertEquals(
 +              "Couldn't parse the minimum value for graduated colour ('blue')",
 +              e.getMessage());
 +    }
 +
 +    /*
       * graduated colour (explicitly by 'score') (no threshold)
       */
      fc = FeatureColour
       * graduated colour based on attribute value for AF
       * given a min-max range of 0-100
       */
-     FeatureColour fc = new FeatureColour(new Color(50, 100, 150),
-             new Color(150, 200, 250), Color.yellow, 0f, 100f);
+     FeatureColour fc = new FeatureColour(Color.white,
+             new Color(50, 100, 150), new Color(150, 200, 250), Color.yellow,
+             0f, 100f);
      String attName = "AF";
      fc.setAttributeName(attName);
  
      Color expected = new Color(70, 120, 170);
      assertEquals(expected, fc.getColor(sf));
    }
 +
 +  @Test(groups = { "Functional" })
 +  public void testIsOutwithThreshold()
 +  {
 +    FeatureColourI fc = new FeatureColour(Color.red);
 +    SequenceFeature sf = new SequenceFeature("METAL", "desc", 10, 12, 1.2f, "grp");
 +    assertFalse(fc.isOutwithThreshold(null));
 +    assertFalse(fc.isOutwithThreshold(sf));
 +
-     fc = new FeatureColour(Color.white, Color.black, Color.green, 0f, 10f);
++    fc = new FeatureColour(null, Color.white, Color.black, Color.green, 0f,
++            10f);
 +    assertFalse(fc.isOutwithThreshold(sf)); // no threshold
 +
 +    fc.setAboveThreshold(true);
 +    fc.setThreshold(1f);
 +    assertFalse(fc.isOutwithThreshold(sf)); // feature score 1.2 is above 1
 +
 +    fc.setThreshold(2f);
 +    assertTrue(fc.isOutwithThreshold(sf)); // feature score 1.2 is not above 2
 +
 +    fc.setBelowThreshold(true);
 +    assertFalse(fc.isOutwithThreshold(sf)); // feature score 1.2 is below 2
 +
 +    fc.setThreshold(1f);
 +    assertTrue(fc.isOutwithThreshold(sf)); // feature score 1.2 is not below 1
 +
 +    /*
 +     * with attribute value threshold
 +     */
 +    fc.setAttributeName("AC");
 +    assertFalse(fc.isOutwithThreshold(sf)); // missing attribute AC is ignored
 +
 +    sf.setValue("AC", "-1");
 +    assertFalse(fc.isOutwithThreshold(sf)); // value -1 is below 1
 +
 +    sf.setValue("AC", "1");
 +    assertTrue(fc.isOutwithThreshold(sf)); // value 1 is not below 1
 +
 +    sf.setValue("AC", "junk");
 +    assertFalse(fc.isOutwithThreshold(sf)); // bad value is ignored
 +  }
  }