JAL-2629 update spike branch to latest
[jalview.git] / src / jalview / gui / Jalview2XML.java
index b357234..aed73d6 100644 (file)
@@ -30,6 +30,7 @@ import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.GraphLine;
+import jalview.datamodel.HiddenMarkovModel;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.RnaViewerModel;
 import jalview.datamodel.SequenceFeature;
@@ -37,10 +38,15 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.StructureViewerModel;
 import jalview.datamodel.StructureViewerModel.StructureData;
+import jalview.datamodel.features.FeatureMatcher;
+import jalview.datamodel.features.FeatureMatcherI;
+import jalview.datamodel.features.FeatureMatcherSet;
+import jalview.datamodel.features.FeatureMatcherSetI;
 import jalview.ext.varna.RnaModel;
 import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
+import jalview.io.HMMFile;
 import jalview.renderer.ResidueShaderI;
 import jalview.schemabinding.version2.AlcodMap;
 import jalview.schemabinding.version2.AlcodonFrame;
@@ -48,6 +54,7 @@ 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;
@@ -60,6 +67,8 @@ 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;
@@ -75,6 +84,9 @@ 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;
@@ -83,10 +95,12 @@ import jalview.schemes.ResidueProperties;
 import jalview.schemes.UserColourScheme;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
+import jalview.util.Format;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.util.StringUtils;
 import jalview.util.jarInputStreamProvider;
+import jalview.util.matcher.Condition;
 import jalview.viewmodel.AlignmentViewport;
 import jalview.viewmodel.ViewportRanges;
 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
@@ -115,6 +129,7 @@ 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.HashMap;
 import java.util.HashSet;
@@ -153,6 +168,8 @@ public class Jalview2XML
 
   private static final String RNA_PREFIX = "rna_";
 
+  private static final String HMMER_PREFIX = "hmmer_";
+
   private static final String UTF_8 = "UTF-8";
 
   // use this with nextCounter() to make unique names for entities
@@ -216,34 +233,6 @@ public class Jalview2XML
     }
   }
 
-  void clearSeqRefs()
-  {
-    if (_cleartables)
-    {
-      if (seqRefIds != null)
-      {
-        seqRefIds.clear();
-      }
-      if (seqsToIds != null)
-      {
-        seqsToIds.clear();
-      }
-      if (incompleteSeqs != null)
-      {
-        incompleteSeqs.clear();
-      }
-      // seqRefIds = null;
-      // seqsToIds = null;
-    }
-    else
-    {
-      // do nothing
-      warn("clearSeqRefs called when _cleartables was not set. Doing nothing.");
-      // seqRefIds = new Hashtable();
-      // seqsToIds = new IdentityHashMap();
-    }
-  }
-
   void initSeqRefs()
   {
     if (seqsToIds == null)
@@ -881,55 +870,28 @@ public class Jalview2XML
         }
       }
 
-      // TODO: omit sequence features from each alignment view's XML dump if we
-      // are storing dataset
-      List<jalview.datamodel.SequenceFeature> sfs = jds
-              .getSequenceFeatures();
+      /*
+       * save sequence features
+       * TODO: omit sequence features from each alignment view's 
+       * XML dump if we are storing dataset
+       */
+      List<SequenceFeature> sfs = jds.getSequenceFeatures();
       for (SequenceFeature sf : sfs)
       {
-        Features features = new Features();
-
-        features.setBegin(sf.getBegin());
-        features.setEnd(sf.getEnd());
-        features.setDescription(sf.getDescription());
-        features.setType(sf.getType());
-        features.setFeatureGroup(sf.getFeatureGroup());
-        features.setScore(sf.getScore());
-        if (sf.links != null)
-        {
-          for (int l = 0; l < sf.links.size(); l++)
-          {
-            OtherData keyValue = new OtherData();
-            keyValue.setKey("LINK_" + l);
-            keyValue.setValue(sf.links.elementAt(l).toString());
-            features.addOtherData(keyValue);
-          }
-        }
-        if (sf.otherDetails != null)
-        {
-          String key;
-          Iterator<String> keys = sf.otherDetails.keySet().iterator();
-          while (keys.hasNext())
-          {
-            key = keys.next();
-            OtherData keyValue = new OtherData();
-            keyValue.setKey(key);
-            keyValue.setValue(sf.otherDetails.get(key).toString());
-            features.addOtherData(keyValue);
-          }
-        }
-
+        Features features = saveFeature(sf);
         jseq.addFeatures(features);
       }
 
+      /*
+       * save PDB entries for sequence
+       */
       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();
+          PDBEntry entry = en.nextElement();
 
           String pdbId = entry.getId();
           pdb.setId(pdbId);
@@ -1013,6 +975,11 @@ public class Jalview2XML
 
       saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
 
+      if (jds.hasHMMProfile())
+      {
+        saveHmmerProfile(jout, jseq, jds);
+      }
+
       jms.addJSeq(jseq);
     }
 
@@ -1084,7 +1051,7 @@ public class Jalview2XML
 
     // SAVE TREES
     // /////////////////////////////////
-    if (!storeDS && av.currentTree != null)
+    if (!storeDS && av.getCurrentTree() != null)
     {
       // FIND ANY ASSOCIATED TREES
       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
@@ -1102,7 +1069,7 @@ public class Jalview2XML
             {
               Tree tree = new Tree();
               tree.setTitle(tp.getTitle());
-              tree.setCurrentTree((av.currentTree == tp.getTree()));
+              tree.setCurrentTree((av.getCurrentTree() == tp.getTree()));
               tree.setNewick(tp.getTree().print());
               tree.setThreshold(tp.treeCanvas.threshold);
 
@@ -1219,7 +1186,7 @@ public class Jalview2XML
         jGroup.setTextCol2(sg.textColour2.getRGB());
         jGroup.setTextColThreshold(sg.thresholdTextColour);
         jGroup.setShowUnconserved(sg.getShowNonconserved());
-        jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
+        jGroup.setIgnoreGapsinConsensus(sg.isIgnoreGapsConsensus());
         jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
         jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
         jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
@@ -1341,19 +1308,33 @@ public class Jalview2XML
       {
         jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings();
 
-        String[] renderOrder = ap.getSeqPanel().seqCanvas
-                .getFeatureRenderer().getRenderOrder()
-                .toArray(new String[0]);
+        FeatureRenderer fr = ap.getSeqPanel().seqCanvas
+                .getFeatureRenderer();
+        String[] renderOrder = fr.getRenderOrder().toArray(new String[0]);
 
         Vector<String> settingsAdded = new Vector<>();
         if (renderOrder != null)
         {
           for (String featureType : renderOrder)
           {
-            FeatureColourI fcol = ap.getSeqPanel().seqCanvas
-                    .getFeatureRenderer().getFeatureStyle(featureType);
             Setting setting = new Setting();
             setting.setType(featureType);
+
+            /*
+             * save any filter for the feature type
+             */
+            FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
+            if (filter != null)  {
+              Iterator<FeatureMatcherI> filters = filter.getMatchers().iterator();
+              FeatureMatcherI firstFilter = filters.next();
+              setting.setMatcherSet(Jalview2XML.marshalFilter(
+                      firstFilter, filters, filter.isAnded()));
+            }
+
+            /*
+             * save colour scheme for the feature type
+             */
+            FeatureColourI fcol = fr.getFeatureStyle(featureType);
             if (!fcol.isSimpleColour())
             {
               setting.setColour(fcol.getMaxColour().getRGB());
@@ -1361,8 +1342,25 @@ public class Jalview2XML
               setting.setMin(fcol.getMin());
               setting.setMax(fcol.getMax());
               setting.setColourByLabel(fcol.isColourByLabel());
+              if (fcol.isColourByAttribute())
+              {
+                setting.setAttributeName(fcol.getAttributeName());
+              }
               setting.setAutoScale(fcol.isAutoScaled());
               setting.setThreshold(fcol.getThreshold());
+              Color noColour = fcol.getNoColour();
+              if (noColour == null)
+              {
+                setting.setNoValueColour(NoValueColour.NONE);
+              }
+              else if (noColour.equals(fcol.getMaxColour()))
+              {
+                setting.setNoValueColour(NoValueColour.MAX);
+              }
+              else
+              {
+                setting.setNoValueColour(NoValueColour.MIN);
+              }
               // -1 = No threshold, 0 = Below, 1 = Above
               setting.setThreshstate(fcol.isAboveThreshold() ? 1
                       : (fcol.isBelowThreshold() ? 0 : -1));
@@ -1374,7 +1372,7 @@ public class Jalview2XML
 
             setting.setDisplay(
                     av.getFeaturesDisplayed().isVisible(featureType));
-            float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
+            float rorder = fr
                     .getOrder(featureType);
             if (rorder > -1)
             {
@@ -1386,8 +1384,7 @@ public class Jalview2XML
         }
 
         // is groups actually supposed to be a map here ?
-        Iterator<String> en = ap.getSeqPanel().seqCanvas
-                .getFeatureRenderer().getFeatureGroups().iterator();
+        Iterator<String> en = fr.getFeatureGroups().iterator();
         Vector<String> groupsAdded = new Vector<>();
         while (en.hasNext())
         {
@@ -1398,8 +1395,7 @@ public class Jalview2XML
           }
           Group g = new Group();
           g.setName(grp);
-          g.setDisplay(((Boolean) ap.getSeqPanel().seqCanvas
-                  .getFeatureRenderer().checkGroupVisibility(grp, false))
+          g.setDisplay(((Boolean) fr.checkGroupVisibility(grp, false))
                           .booleanValue());
           fs.addGroup(g);
           groupsAdded.addElement(grp);
@@ -1417,9 +1413,10 @@ public class Jalview2XML
         }
         else
         {
-          ArrayList<int[]> hiddenRegions = hidden.getHiddenColumnsCopy();
-          for (int[] region : hiddenRegions)
+          Iterator<int[]> hiddenRegions = hidden.iterator();
+          while (hiddenRegions.hasNext())
           {
+            int[] region = hiddenRegions.next();
             HiddenColumns hc = new HiddenColumns();
             hc.setStart(region[0]);
             hc.setEnd(region[1]);
@@ -1474,6 +1471,98 @@ public class Jalview2XML
   }
 
   /**
+   * Saves the HMMER profile associated with the sequence as a file in the jar,
+   * in HMMER format, and saves the name of the file as a child element of the
+   * XML sequence element
+   * 
+   * @param jout
+   * @param xmlSeq
+   * @param seq
+   */
+  protected void saveHmmerProfile(JarOutputStream jout, JSeq xmlSeq,
+          SequenceI seq)
+  {
+    HiddenMarkovModel profile = seq.getHMM();
+    if (profile == null)
+    {
+      warn("Want to save HMM profile for " + seq.getName()
+              + " but none found");
+      return;
+    }
+    HMMFile hmmFile = new HMMFile(profile);
+    String hmmAsString = hmmFile.print();
+    String jarEntryName = HMMER_PREFIX + nextCounter();
+    try
+    {
+      writeJarEntry(jout, jarEntryName, hmmAsString.getBytes());
+      xmlSeq.setHmmerProfile(jarEntryName);
+    } catch (IOException e)
+    {
+      warn("Error saving HMM profile: " + e.getMessage());
+    }
+  }
+
+  /**
+   * Converts a Jalview SequenceFeature into the XML model of it to save
+   * 
+   * @param sf
+   * @return
+   */
+  protected Features saveFeature(SequenceFeature sf)
+  {
+    Features features = new Features();
+
+    features.setBegin(sf.getBegin());
+    features.setEnd(sf.getEnd());
+    features.setDescription(sf.getDescription());
+    features.setType(sf.getType());
+    features.setFeatureGroup(sf.getFeatureGroup());
+    features.setScore(sf.getScore());
+    if (sf.links != null)
+    {
+      for (int l = 0; l < sf.links.size(); l++)
+      {
+        OtherData keyValue = new OtherData();
+        keyValue.setKey("LINK_" + l);
+        keyValue.setValue(sf.links.elementAt(l).toString());
+        features.addOtherData(keyValue);
+      }
+    }
+    if (sf.otherDetails != null)
+    {
+      /*
+       * save feature attributes, which may be simple strings or
+       * map valued (have sub-attributes)
+       */
+      for (Entry<String, Object> entry : sf.otherDetails.entrySet())
+      {
+        String key = entry.getKey();
+        Object value = entry.getValue();
+        if (value instanceof Map<?, ?>)
+        {
+          for (Entry<String, Object> subAttribute : ((Map<String, Object>) value)
+                  .entrySet())
+          {
+            OtherData otherData = new OtherData();
+            otherData.setKey(key);
+            otherData.setKey2(subAttribute.getKey());
+            otherData.setValue(subAttribute.getValue().toString());
+            features.addOtherData(otherData);
+          }
+        }
+        else
+        {
+          OtherData otherData = new OtherData();
+          otherData.setKey(key);
+          otherData.setValue(value.toString());
+          features.addOtherData(otherData);
+        }
+      }
+    }
+    return features;
+  }
+
+  /**
    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
    * for each viewer, with
    * <ul>
@@ -1674,9 +1763,8 @@ public class Jalview2XML
       }
       else if (!matchedFile.equals(pdbentry.getFile()))
       {
-        Cache.log.warn(
-                "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
-                        + pdbentry.getFile());
+        warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
+                + pdbentry.getFile());
       }
       // record the
       // file so we
@@ -2160,12 +2248,12 @@ public class Jalview2XML
         mpc.setDseqFor(jmpid);
         if (!seqRefIds.containsKey(mpc.getDseqFor()))
         {
-          jalview.bin.Cache.log.debug("creatign new DseqFor ID");
+          debug("creating new DseqFor ID");
           seqRefIds.put(mpc.getDseqFor(), ps);
         }
         else
         {
-          jalview.bin.Cache.log.debug("reusing DseqFor ID");
+          debug("reusing DseqFor ID");
         }
 
         mp.setMappingChoice(mpc);
@@ -2980,40 +3068,68 @@ public class Jalview2XML
       //
       for (int i = 0; i < vamsasSeq.length; i++)
       {
+        SequenceI alignmentSeq = al.getSequenceAt(i);
         if (jseqs[i].getFeaturesCount() > 0)
         {
           Features[] features = jseqs[i].getFeatures();
           for (int f = 0; f < features.length; 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());
-            for (int od = 0; od < features[f].getOtherDataCount(); od++)
+            Features feature = features[f];
+            SequenceFeature sf = new SequenceFeature(feature.getType(),
+                    feature.getDescription(), feature.getBegin(),
+                    feature.getEnd(), feature.getScore(),
+                    feature.getFeatureGroup());
+            sf.setStatus(feature.getStatus());
+
+            /*
+             * load any feature attributes - include map-valued attributes
+             */
+            Map<String, Map<String, String>> mapAttributes = new HashMap<>();
+            for (int od = 0; od < feature.getOtherDataCount(); od++)
             {
-              OtherData keyValue = features[f].getOtherData(od);
-              if (keyValue.getKey().startsWith("LINK"))
+              OtherData keyValue = feature.getOtherData(od);
+              String attributeName = keyValue.getKey();
+              String attributeValue = keyValue.getValue();
+              if (attributeName.startsWith("LINK"))
               {
-                sf.addLink(keyValue.getValue());
+                sf.addLink(attributeValue);
               }
               else
               {
-                sf.setValue(keyValue.getKey(), keyValue.getValue());
+                String subAttribute = keyValue.getKey2();
+                if (subAttribute == null)
+                {
+                  // simple string-valued attribute
+                  sf.setValue(attributeName, attributeValue);
+                }
+                else
+                {
+                  // attribute 'key' has sub-attribute 'key2'
+                  if (!mapAttributes.containsKey(attributeName))
+                  {
+                    mapAttributes.put(attributeName, new HashMap<>());
+                  }
+                  mapAttributes.get(attributeName).put(subAttribute,
+                          attributeValue);
+                }
               }
-
             }
+            for (Entry<String, Map<String, String>> mapAttribute : mapAttributes
+                    .entrySet())
+            {
+              sf.setValue(mapAttribute.getKey(), mapAttribute.getValue());
+            }
+
             // adds feature to datasequence's feature set (since Jalview 2.10)
-            al.getSequenceAt(i).addSequenceFeature(sf);
+            alignmentSeq.addSequenceFeature(sf);
           }
         }
         if (vamsasSeq[i].getDBRefCount() > 0)
         {
           // adds dbrefs to datasequence's set (since Jalview 2.10)
           addDBRefs(
-                  al.getSequenceAt(i).getDatasetSequence() == null
-                          ? al.getSequenceAt(i)
-                          : al.getSequenceAt(i).getDatasetSequence(),
+                  alignmentSeq.getDatasetSequence() == null ? alignmentSeq
+                          : alignmentSeq.getDatasetSequence(),
                   vamsasSeq[i]);
         }
         if (jseqs[i].getPdbidsCount() > 0)
@@ -3061,16 +3177,25 @@ public class Jalview2XML
                     .getStructureSelectionManager(Desktop.instance)
                     .registerPDBEntry(entry);
             // adds PDBEntry to datasequence's set (since Jalview 2.10)
-            if (al.getSequenceAt(i).getDatasetSequence() != null)
+            if (alignmentSeq.getDatasetSequence() != null)
             {
-              al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
+              alignmentSeq.getDatasetSequence().addPDBId(entry);
             }
             else
             {
-              al.getSequenceAt(i).addPDBId(entry);
+              alignmentSeq.addPDBId(entry);
             }
           }
         }
+
+        /*
+         * load any HMMER profile
+         */
+        String hmmJarFile = jseqs[i].getHmmerProfile();
+        if (hmmJarFile != null)
+        {
+          loadHmmerProfile(jprovider, hmmJarFile, alignmentSeq);
+        }
       }
     } // end !multipleview
 
@@ -3568,6 +3693,31 @@ public class Jalview2XML
   }
 
   /**
+   * Loads a HMMER profile from a file stored in the project, and associates it
+   * with the specified sequence
+   * 
+   * @param jprovider
+   * @param hmmJarFile
+   * @param seq
+   */
+  protected void loadHmmerProfile(jarInputStreamProvider jprovider,
+          String hmmJarFile, SequenceI seq)
+  {
+    try
+    {
+      String hmmFile = copyJarEntry(jprovider, hmmJarFile, "hmm", null);
+      HMMFile parser = new HMMFile(hmmFile, DataSourceType.FILE);
+      HiddenMarkovModel hmmModel = parser.getHMM();
+      hmmModel = new HiddenMarkovModel(hmmModel, seq);
+      seq.setHMM(hmmModel);
+    } catch (IOException e)
+    {
+      warn("Error loading HMM profile for " + seq.getName() + ": "
+              + e.getMessage());
+    }
+  }
+
+  /**
    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
    * panel is restored from separate jar entries, two (gapped and trimmed) per
    * sequence and secondary structure.
@@ -4250,7 +4400,8 @@ public class Jalview2XML
       StructureData filedat = oldFiles.get(id);
       String pdbFile = filedat.getFilePath();
       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
-      binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE);
+      binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
+              null);
       binding.addSequenceForStructFile(pdbFile, seq);
     }
     // and add the AlignmentPanel's reference to the view panel
@@ -4499,7 +4650,7 @@ public class Jalview2XML
     af.viewport.getResidueShading().setThreshold(view.getPidThreshold(),
             view.getIgnoreGapsinConsensus());
     af.viewport.getResidueShading()
-            .setConsensus(af.viewport.getSequenceConsensusHash());
+            .setConsensus(af.viewport.getConsensusProfiles());
     af.viewport.setColourAppliesToAllGroups(false);
 
     if (view.getConservationSelected() && cs != null)
@@ -4577,9 +4728,11 @@ public class Jalview2XML
       af.viewport.setShowGroupConservation(false);
     }
 
-    // recover featre settings
+    // recover feature settings
     if (jms.getFeatureSettings() != null)
     {
+      FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
+              .getFeatureRenderer();
       FeaturesDisplayed fdi;
       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
       String[] renderOrder = new String[jms.getFeatureSettings()
@@ -4591,14 +4744,51 @@ public class Jalview2XML
               .getSettingCount(); fs++)
       {
         Setting setting = jms.getFeatureSettings().getSetting(fs);
+        String featureType = setting.getType();
+
+        /*
+         * restore feature filters (if any)
+         */
+        MatcherSet filters = setting.getMatcherSet();
+        if (filters != null)
+        {
+          FeatureMatcherSetI filter = Jalview2XML
+                  .unmarshalFilter(featureType, filters);
+          if (!filter.isEmpty())
+          {
+            fr.setFeatureFilter(featureType, filter);
+          }
+        }
+
+        /*
+         * restore feature colour scheme
+         */
+        Color maxColour = new Color(setting.getColour());
         if (setting.hasMincolour())
         {
-          FeatureColourI gc = setting.hasMin()
-                  ? new FeatureColour(new Color(setting.getMincolour()),
-                          new Color(setting.getColour()), setting.getMin(),
-                          setting.getMax())
-                  : new FeatureColour(new Color(setting.getMincolour()),
-                          new Color(setting.getColour()), 0, 1);
+          /*
+           * 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 noValueColour = minColour;
+          NoValueColour noColour = setting.getNoValueColour();
+          if (noColour == NoValueColour.NONE)
+          {
+            noValueColour = null;
+          }
+          else if (noColour == NoValueColour.MAX)
+          {
+            noValueColour = maxColour;
+          }
+          float min = setting.hasMin() ? setting.getMin() : 0f;
+          float max = setting.hasMin() ? setting.getMax() : 1f;
+          FeatureColourI gc = new FeatureColour(minColour, maxColour,
+                  noValueColour, min, max);
+          if (setting.getAttributeNameCount() > 0)
+          {
+            gc.setAttributeName(setting.getAttributeName());
+          }
           if (setting.hasThreshold())
           {
             gc.setThreshold(setting.getThreshold());
@@ -4623,26 +4813,26 @@ public class Jalview2XML
             gc.setColourByLabel(setting.getColourByLabel());
           }
           // and put in the feature colour table.
-          featureColours.put(setting.getType(), gc);
+          featureColours.put(featureType, gc);
         }
         else
         {
-          featureColours.put(setting.getType(),
-                  new FeatureColour(new Color(setting.getColour())));
+          featureColours.put(featureType,
+                  new FeatureColour(maxColour));
         }
-        renderOrder[fs] = setting.getType();
+        renderOrder[fs] = featureType;
         if (setting.hasOrder())
         {
-          featureOrder.put(setting.getType(), setting.getOrder());
+          featureOrder.put(featureType, setting.getOrder());
         }
         else
         {
-          featureOrder.put(setting.getType(), new Float(
+          featureOrder.put(featureType, new Float(
                   fs / jms.getFeatureSettings().getSettingCount()));
         }
         if (setting.getDisplay())
         {
-          fdi.setVisible(setting.getType());
+          fdi.setVisible(featureType);
         }
       }
       Map<String, Boolean> fgtable = new Hashtable<>();
@@ -4656,9 +4846,7 @@ public class Jalview2XML
       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
               fgtable, featureColours, 1.0f, featureOrder);
-      af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
-              .transferSettings(frs);
-
+      fr.transferSettings(frs);
     }
 
     if (view.getHiddenColumnsCount() > 0)
@@ -4956,10 +5144,7 @@ public class Jalview2XML
             id = object.getJalviewModelSequence().getViewport()[0]
                     .getSequenceSetId()))
     {
-      if (Cache.log != null && Cache.log.isDebugEnabled())
-      {
-        Cache.log.debug("Skipping seuqence set id " + id);
-      }
+      debug("Skipping sequence set id " + id);
       return true;
     }
     return false;
@@ -5333,7 +5518,7 @@ public class Jalview2XML
           seqRefIds.put(sqid, djs);
 
         }
-        jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
+        debug("about to recurse on addDBRefs.");
         addDBRefs(djs, ms);
 
       }
@@ -5342,28 +5527,25 @@ public class Jalview2XML
 
   }
 
-  public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
-          boolean keepSeqRefs)
+  /**
+   * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
+   * view as XML (but not to file), and then reloading it
+   * 
+   * @param ap
+   * @return
+   */
+  public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
   {
     initSeqRefs();
     JalviewModel jm = saveState(ap, null, null, null);
 
-    if (!keepSeqRefs)
-    {
-      clearSeqRefs();
-      jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
-    }
-    else
-    {
-      uniqueSetSuffix = "";
-      jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
-      // overwrite the
-      // view we just
-      // copied
-    }
+    uniqueSetSuffix = "";
+    jm.getJalviewModelSequence().getViewport(0).setId(null);
+    // we don't overwrite the view we just copied
+
     if (this.frefedSequence == null)
     {
-      frefedSequence = new Vector();
+      frefedSequence = new Vector<>();
     }
 
     viewportsAdded.clear();
@@ -5383,32 +5565,8 @@ public class Jalview2XML
     return af.alignPanel;
   }
 
-  /**
-   * flag indicating if hashtables should be cleared on finalization TODO this
-   * flag may not be necessary
-   */
-  private final boolean _cleartables = true;
-
   private Hashtable jvids2vobj;
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see java.lang.Object#finalize()
-   */
-  @Override
-  protected void finalize() throws Throwable
-  {
-    // really make sure we have no buried refs left.
-    if (_cleartables)
-    {
-      clearSeqRefs();
-    }
-    this.seqRefIds = null;
-    this.seqsToIds = null;
-    super.finalize();
-  }
-
   private void warn(String msg)
   {
     warn(msg, null);
@@ -5538,7 +5696,7 @@ public class Jalview2XML
       }
       else
       {
-        Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
+        debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
       }
     }
   }
@@ -5639,4 +5797,292 @@ public class Jalview2XML
   {
     return counter++;
   }
+
+  /**
+   * 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(
+          String featureType, FeatureColourI fcol)
+  {
+    jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
+    if (fcol.isSimpleColour())
+    {
+      col.setRGB(Format.getHexString(fcol.getColour()));
+    }
+    else
+    {
+      col.setRGB(Format.getHexString(fcol.getMaxColour()));
+      col.setMin(fcol.getMin());
+      col.setMax(fcol.getMax());
+      col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
+      col.setAutoScale(fcol.isAutoScaled());
+      col.setThreshold(fcol.getThreshold());
+      col.setColourByLabel(fcol.isColourByLabel());
+      col.setThreshType(fcol.isAboveThreshold() ? ColourThreshTypeType.ABOVE
+              : (fcol.isBelowThreshold() ? ColourThreshTypeType.BELOW
+                      : ColourThreshTypeType.NONE));
+      if (fcol.isColourByAttribute())
+      {
+        col.setAttributeName(fcol.getAttributeName());
+      }
+      Color noColour = fcol.getNoColour();
+      if (noColour == null)
+      {
+        col.setNoValueColour(NoValueColour.NONE);
+      }
+      else if (noColour == fcol.getMaxColour())
+      {
+        col.setNoValueColour(NoValueColour.MAX);
+      }
+      else
+      {
+        col.setNoValueColour(NoValueColour.MIN);
+      }
+    }
+    col.setName(featureType);
+    return col;
+  }
+
+  /**
+   * Populates an XML model of the feature filter(s) for one feature type
+   * 
+   * @param firstMatcher
+   *          the first (or only) match condition)
+   * @param filter
+   *          remaining match conditions (if any)
+   * @param and
+   *          if true, conditions are and-ed, else or-ed
+   */
+  protected static MatcherSet marshalFilter(FeatureMatcherI firstMatcher,
+          Iterator<FeatureMatcherI> filters, boolean and)
+  {
+    MatcherSet result = new MatcherSet();
+  
+    if (filters.hasNext())
+    {
+      /*
+       * compound matcher
+       */
+      CompoundMatcher compound = new CompoundMatcher();
+      compound.setAnd(and);
+      MatcherSet matcher1 = marshalFilter(firstMatcher,
+              Collections.emptyIterator(), and);
+      compound.addMatcherSet(matcher1);
+      FeatureMatcherI nextMatcher = filters.next();
+      MatcherSet matcher2 = marshalFilter(nextMatcher, filters, and);
+      compound.addMatcherSet(matcher2);
+      result.setCompoundMatcher(compound);
+    }
+    else
+    {
+      /*
+       * single condition matcher
+       */
+      MatchCondition matcherModel = new MatchCondition();
+      matcherModel.setCondition(
+              firstMatcher.getMatcher().getCondition().getStableName());
+      matcherModel.setValue(firstMatcher.getMatcher().getPattern());
+      if (firstMatcher.isByAttribute())
+      {
+        matcherModel.setBy(FeatureMatcherByType.BYATTRIBUTE);
+        matcherModel.setAttributeName(firstMatcher.getAttribute());
+      }
+      else if (firstMatcher.isByLabel())
+      {
+        matcherModel.setBy(FeatureMatcherByType.BYLABEL);
+      }
+      else if (firstMatcher.isByScore())
+      {
+        matcherModel.setBy(FeatureMatcherByType.BYSCORE);
+      }
+      result.setMatchCondition(matcherModel);
+    }
+  
+    return result;
+  }
+
+  /**
+   * Loads one XML model of a feature filter to a Jalview object
+   * 
+   * @param featureType
+   * @param matcherSetModel
+   * @return
+   */
+  protected static FeatureMatcherSetI unmarshalFilter(
+          String featureType, MatcherSet matcherSetModel)
+  {
+    FeatureMatcherSetI result = new FeatureMatcherSet();
+    try
+    {
+      unmarshalFilterConditions(result, matcherSetModel, true);
+    } catch (IllegalStateException e)
+    {
+      // mixing AND and OR conditions perhaps
+      System.err.println(
+              String.format("Error reading filter conditions for '%s': %s",
+                      featureType, e.getMessage()));
+      // return as much as was parsed up to the error
+    }
+  
+    return result;
+  }
+
+  /**
+   * Adds feature match conditions to matcherSet as unmarshalled from XML
+   * (possibly recursively for compound conditions)
+   * 
+   * @param matcherSet
+   * @param matcherSetModel
+   * @param and
+   *          if true, multiple conditions are AND-ed, else they are OR-ed
+   * @throws IllegalStateException
+   *           if AND and OR conditions are mixed
+   */
+  protected static void unmarshalFilterConditions(
+          FeatureMatcherSetI matcherSet, MatcherSet matcherSetModel,
+          boolean and)
+  {
+    MatchCondition mc = matcherSetModel.getMatchCondition();
+    if (mc != null)
+    {
+      /*
+       * single condition
+       */
+      FeatureMatcherByType filterBy = mc.getBy();
+      Condition cond = Condition.fromString(mc.getCondition());
+      String pattern = mc.getValue();
+      FeatureMatcherI matchCondition = null;
+      if (filterBy == FeatureMatcherByType.BYLABEL)
+      {
+        matchCondition = FeatureMatcher.byLabel(cond, pattern);
+      }
+      else if (filterBy == FeatureMatcherByType.BYSCORE)
+      {
+        matchCondition = FeatureMatcher.byScore(cond, pattern);
+  
+      }
+      else if (filterBy == FeatureMatcherByType.BYATTRIBUTE)
+      {
+        String[] attNames = mc.getAttributeName();
+        matchCondition = FeatureMatcher.byAttribute(cond, pattern,
+                attNames);
+      }
+  
+      /*
+       * note this throws IllegalStateException if AND-ing to a 
+       * previously OR-ed compound condition, or vice versa
+       */
+      if (and)
+      {
+        matcherSet.and(matchCondition);
+      }
+      else
+      {
+        matcherSet.or(matchCondition);
+      }
+    }
+    else
+    {
+      /*
+       * compound condition
+       */
+      MatcherSet[] matchers = matcherSetModel.getCompoundMatcher()
+              .getMatcherSet();
+      boolean anded = matcherSetModel.getCompoundMatcher().getAnd();
+      if (matchers.length == 2)
+      {
+        unmarshalFilterConditions(matcherSet, matchers[0], anded);
+        unmarshalFilterConditions(matcherSet, matchers[1], anded);
+      }
+      else
+      {
+        System.err.println("Malformed compound filter condition");
+      }
+    }
+  }
+
+  /**
+   * Loads one XML model of a feature colour to a Jalview object
+   * 
+   * @param colourModel
+   * @return
+   */
+  protected static FeatureColourI unmarshalColour(
+          jalview.schemabinding.version2.Colour colourModel)
+  {
+    FeatureColourI colour = null;
+  
+    if (colourModel.hasMax())
+    {
+      Color mincol = null;
+      Color maxcol = null;
+      Color noValueColour = null;
+  
+      try
+      {
+        mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
+        maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
+      } catch (Exception e)
+      {
+        if (Cache.log != null)
+        {
+          Cache.log.warn("Couldn't parse out graduated feature color.", e);
+        }
+      }
+  
+      NoValueColour noCol = colourModel.getNoValueColour();
+      if (noCol == NoValueColour.MIN)
+      {
+        noValueColour = mincol;
+      }
+      else if (noCol == NoValueColour.MAX)
+      {
+        noValueColour = maxcol;
+      }
+  
+      colour = new FeatureColour(mincol, maxcol, noValueColour,
+              colourModel.getMin(),
+              colourModel.getMax());
+      String[] attributes = colourModel.getAttributeName();
+      if (attributes != null && attributes.length > 0)
+      {
+        colour.setAttributeName(attributes);
+      }
+      if (colourModel.hasAutoScale())
+      {
+        colour.setAutoScaled(colourModel.getAutoScale());
+      }
+      if (colourModel.hasColourByLabel())
+      {
+        colour.setColourByLabel(colourModel.getColourByLabel());
+      }
+      if (colourModel.hasThreshold())
+      {
+        colour.setThreshold(colourModel.getThreshold());
+      }
+      ColourThreshTypeType ttyp = colourModel.getThreshType();
+      if (ttyp != null)
+      {
+        if (ttyp == ColourThreshTypeType.ABOVE)
+        {
+          colour.setAboveThreshold(true);
+        }
+        else if (ttyp == ColourThreshTypeType.BELOW)
+        {
+          colour.setBelowThreshold(true);
+        }
+      }
+    }
+    else
+    {
+      Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
+      colour = new FeatureColour(color);
+    }
+  
+    return colour;
+  }
 }