JAL-2381 copy and paste sequence also copies contact matrix annotation
[jalview.git] / src / jalview / datamodel / Sequence.java
index 6cf0646..32d295a 100755 (executable)
  */
 package jalview.datamodel;
 
-import jalview.analysis.AlignSeq;
-import jalview.datamodel.features.SequenceFeatures;
-import jalview.datamodel.features.SequenceFeaturesI;
-import jalview.util.Comparison;
-import jalview.util.DBRefUtils;
-import jalview.util.MapList;
-import jalview.util.StringUtils;
-import jalview.ws.params.InvalidArgumentException;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.Iterator;
@@ -40,6 +32,16 @@ import java.util.ListIterator;
 import java.util.Vector;
 
 import fr.orsay.lri.varna.models.rna.RNA;
+import jalview.analysis.AlignSeq;
+import jalview.analysis.AlignmentUtils;
+import jalview.analysis.SeqsetUtils;
+import jalview.datamodel.features.SequenceFeatures;
+import jalview.datamodel.features.SequenceFeaturesI;
+import jalview.util.Comparison;
+import jalview.util.DBRefUtils;
+import jalview.util.MapList;
+import jalview.util.StringUtils;
+import jalview.ws.datamodel.alphafold.MappableContactMatrix;
 
 /**
  * 
@@ -48,39 +50,42 @@ import fr.orsay.lri.varna.models.rna.RNA;
 public class Sequence extends ASequence implements SequenceI
 {
 
-       /**
-        * A subclass that gives us access to modCount, which tracks 
-        * whether there have been any changes. We use this to update 
-        * @author hansonr
-        *
-        * @param <T>
-        */
+  /**
+   * A subclass that gives us access to modCount, which tracks whether there
+   * have been any changes. We use this to update
+   * 
+   * @author hansonr
+   *
+   * @param <T>
+   */
   @SuppressWarnings("serial")
-  protected class DBModList<T> extends ArrayList<DBRefEntry> {
+  public class DBModList<T> extends ArrayList<DBRefEntry>
+  {
+
+    protected int getModCount()
+    {
+      return modCount;
+    }
 
-         protected int getModCount() {
-                 return modCount;
-         }
-         
   }
 
-SequenceI datasetSequence;
+  SequenceI datasetSequence;
 
-  String name;
+  private String name;
 
   private char[] sequence;
 
-  String description;
+  private String description;
 
-  int start;
+  private int start;
 
-  int end;
+  private int end;
 
-  Vector<PDBEntry> pdbIds;
+  private Vector<PDBEntry> pdbIds;
 
-  String vamsasId;
+  private String vamsasId;
 
-  private DBModList<DBRefEntry> dbrefs; // controlled acces
+  private DBModList<DBRefEntry> dbrefs; // controlled access
 
   /**
    * a flag to let us know that elements have changed in dbrefs
@@ -89,15 +94,15 @@ SequenceI datasetSequence;
    */
   private int refModCount = 0;
 
-  RNA rna;
-  
+  private RNA rna;
+
   /**
    * This annotation is displayed below the alignment but the positions are tied
    * to the residues of this sequence
    *
    * TODO: change to List<>
    */
-  Vector<AlignmentAnnotation> annotation;
+  private Vector<AlignmentAnnotation> annotation;
 
   private SequenceFeaturesI sequenceFeatureStore;
 
@@ -302,7 +307,7 @@ SequenceI datasetSequence;
     {
       setDatasetSequence(seq.getDatasetSequence());
     }
-    
+
     /*
      * only copy DBRefs and seqfeatures if we really are a dataset sequence
      */
@@ -347,6 +352,11 @@ SequenceI datasetSequence;
         {
           // only copy the given annotation
           AlignmentAnnotation newann = new AlignmentAnnotation(sqann[i]);
+          ContactMatrixI cm = seq.getContactMatrixFor(sqann[i]);
+          if (cm!=null)
+          {
+            addContactListFor(newann, cm);
+          }
           addAlignmentAnnotation(newann);
         }
       }
@@ -377,8 +387,8 @@ SequenceI datasetSequence;
   {
     if (sf.getType() == null)
     {
-      System.err.println("SequenceFeature type may not be null: "
-              + sf.toString());
+      System.err.println(
+              "SequenceFeature type may not be null: " + sf.toString());
       return false;
     }
 
@@ -470,18 +480,19 @@ SequenceI datasetSequence;
   }
 
   /**
-   * DOCUMENT ME!
+   * Answers the sequence name, with '/start-end' appended if jvsuffix is true
    * 
-   * @return DOCUMENT ME!
+   * @return
    */
   @Override
   public String getDisplayId(boolean jvsuffix)
   {
-    StringBuffer result = new StringBuffer(name);
-    if (jvsuffix)
+    if (!jvsuffix)
     {
-      result.append("/" + start + "-" + end);
+      return name;
     }
+    StringBuilder result = new StringBuilder(name);
+    result.append("/").append(start).append("-").append(end);
 
     return result.toString();
   }
@@ -520,6 +531,7 @@ SequenceI datasetSequence;
   public void setStart(int start)
   {
     this.start = start;
+    sequenceChanged();
   }
 
   /**
@@ -597,8 +609,8 @@ SequenceI datasetSequence;
   public char[] getSequence()
   {
     // return sequence;
-    return sequence == null ? null : Arrays.copyOf(sequence,
-            sequence.length);
+    return sequence == null ? null
+            : Arrays.copyOf(sequence, sequence.length);
   }
 
   /*
@@ -694,8 +706,8 @@ SequenceI datasetSequence;
   public void setGeneLoci(String speciesId, String assemblyId,
           String chromosomeId, MapList map)
   {
-    addDBRef(new DBRefEntry(speciesId, assemblyId, DBRefEntry.CHROMOSOME
-            + ":" + chromosomeId, new Mapping(map)));
+    addDBRef(new GeneLocus(speciesId, assemblyId, chromosomeId,
+            new Mapping(map)));
   }
 
   /**
@@ -711,36 +723,9 @@ SequenceI datasetSequence;
     {
       for (final DBRefEntry ref : refs)
       {
-        if (ref.isChromosome())
+        if (ref instanceof GeneLociI)
         {
-          return new GeneLociI()
-          {
-            @Override
-            public String getSpeciesId()
-            {
-              return ref.getSource();
-            }
-
-            @Override
-            public String getAssemblyId()
-            {
-              return ref.getVersion();
-            }
-
-            @Override
-            public String getChromosomeId()
-            {
-              // strip off "chromosome:" prefix to chrId
-              return ref.getAccessionId().substring(
-                      DBRefEntry.CHROMOSOME.length() + 1);
-            }
-
-            @Override
-            public MapList getMap()
-            {
-              return ref.getMap().getMap();
-            }
-          };
+          return (GeneLociI) ref;
         }
       }
     }
@@ -903,7 +888,7 @@ SequenceI datasetSequence;
     {
       return findPosition(column + 1, cursor);
     }
-    
+
     // TODO recode this more naturally i.e. count residues only
     // as they are found, not 'in anticipation'
 
@@ -1090,7 +1075,7 @@ SequenceI datasetSequence;
    * {@inheritDoc}
    */
   @Override
-  public Range findPositions(int fromColumn, int toColumn)
+  public ContiguousI findPositions(int fromColumn, int toColumn)
   {
     if (toColumn < fromColumn || fromColumn < 1)
     {
@@ -1208,7 +1193,7 @@ SequenceI datasetSequence;
   {
     ArrayList<int[]> map = new ArrayList<>();
     int lastj = -1, j = 0;
-    int pos = start;
+    // int pos = start;
     int seqlen = sequence.length;
     while ((j < seqlen))
     {
@@ -1242,7 +1227,7 @@ SequenceI datasetSequence;
   {
     BitSet map = new BitSet();
     int lastj = -1, j = 0;
-    int pos = start;
+    // int pos = start;
     int seqlen = sequence.length;
     while ((j < seqlen))
     {
@@ -1410,35 +1395,22 @@ SequenceI datasetSequence;
     vamsasId = id;
   }
 
-  @SuppressWarnings("deprecation")
-@Override
-  public void setDBRefs(List<DBRefEntry> newDBrefs) throws InvalidArgumentException
+  @Deprecated
+  @Override
+  public void setDBRefs(DBModList<DBRefEntry> newDBrefs)
   {
     if (dbrefs == null && datasetSequence != null
             && this != datasetSequence)
     {
-      datasetSequence.setDBRefs((DBModList<DBRefEntry>)newDBrefs);
+      datasetSequence.setDBRefs(newDBrefs);
       return;
     }
-    if (newDBrefs != null && !(newDBrefs instanceof DBModList<?>))
-       throw new InvalidArgumentException("DBrefs must have DBModList class");
-
-    dbrefs = (DBModList<DBRefEntry>)newDBrefs;
+    dbrefs = newDBrefs;
     refModCount = 0;
   }
 
   @Override
-  public void getDBRefsFrom(SequenceI seq) {
-         try {
-               setDBRefs(seq.getDBRefs());
-       } catch (InvalidArgumentException e) {
-               // TODO Auto-generated catch block
-               e.printStackTrace();
-       }
-  }
-
-  @Override
-  public List<DBRefEntry> getDBRefs()
+  public DBModList<DBRefEntry> getDBRefs()
   {
     if (dbrefs == null && datasetSequence != null
             && this != datasetSequence)
@@ -1448,10 +1420,10 @@ SequenceI datasetSequence;
     return dbrefs;
   }
 
-
   @Override
   public void addDBRef(DBRefEntry entry)
   {
+    // TODO JAL-3980 maintain as sorted list
     if (datasetSequence != null)
     {
       datasetSequence.addDBRef(entry);
@@ -1460,10 +1432,11 @@ SequenceI datasetSequence;
 
     if (dbrefs == null)
     {
-      dbrefs = new DBModList<DBRefEntry>();    
+      dbrefs = new DBModList<>();
     }
+    // TODO JAL-3979 LOOK UP RATHER THAN SWEEP FOR EFFICIENCY
 
-    for (int ib = 0, nb= dbrefs.size(); ib < nb; ib++)
+    for (int ib = 0, nb = dbrefs.size(); ib < nb; ib++)
     {
       if (dbrefs.get(ib).updateFrom(entry))
       {
@@ -1475,19 +1448,18 @@ SequenceI datasetSequence;
       }
     }
 
+    // /// BH OUCH!
+    // /*
+    // * extend the array to make room for one more
+    // */
+    // // TODO use an ArrayList instead
+    // int j = dbrefs.length;
+    // List<DBRefEntry> temp = new DBRefEntry[j + 1];
+    // System.arraycopy(dbrefs, 0, temp, 0, j);
+    // temp[temp.length - 1] = entry;
+    //
+    // dbrefs = temp;
 
-//    ///  BH OUCH!
-//    /*
-//     * extend the array to make room for one more
-//     */
-//    // TODO use an ArrayList instead
-//    int j = dbrefs.length;
-//    List<DBRefEntry> temp = new DBRefEntry[j + 1];
-//    System.arraycopy(dbrefs, 0, temp, 0, j);
-//    temp[temp.length - 1] = entry;
-//
-//    dbrefs = temp;
-    
     dbrefs.add(entry);
   }
 
@@ -1601,7 +1573,7 @@ SequenceI datasetSequence;
 
   private int _seqhash = 0;
 
-private List<DBRefEntry> primaryRefs;
+  private List<DBRefEntry> primaryRefs;
 
   /**
    * Answers false if the sequence is more than 85% nucleotide (ACGTU), else
@@ -1620,7 +1592,7 @@ private List<DBRefEntry> primaryRefs;
       _isNa = Comparison.isNucleotide(this);
     }
     return !_isNa;
-  };
+  }
 
   /*
    * (non-Javadoc)
@@ -1661,8 +1633,20 @@ private List<DBRefEntry> primaryRefs;
           _aa.adjustForAlignment(); // uses annotation's own record of
                                     // sequence-column mapping
           datasetSequence.addAlignmentAnnotation(_aa);
+
+          if (_cmholder != null)
+          { // transfer contact matrices
+            ContactMatrixI cm = _cmholder.getContactMatrixFor(aa);
+            if (cm != null)
+            {
+              datasetSequence.addContactListFor(_aa, cm);
+              datasetSequence.addContactListFor(aa, cm);
+            }
+          }
         }
       }
+      // all matrices should have been transferred. so we clear the local holder
+      _cmholder=null;
     }
     return datasetSequence;
   }
@@ -1777,8 +1761,9 @@ private List<DBRefEntry> primaryRefs;
       List<SequenceFeature> sfs = entry.getSequenceFeatures();
       for (SequenceFeature feature : sfs)
       {
-       SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
-                : new SequenceFeature[] { new SequenceFeature(feature) };
+        SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
+                : new SequenceFeature[]
+                { new SequenceFeature(feature) };
         if (sf != null)
         {
           for (int sfi = 0; sfi < sf.length; sfi++)
@@ -1836,13 +1821,30 @@ private List<DBRefEntry> primaryRefs;
   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
           String label)
   {
+    return getAlignmentAnnotations(calcId, label, null, true);
+  }
+
+  @Override
+  public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
+          String label, String description)
+  {
+    return getAlignmentAnnotations(calcId, label, description, false);
+  }
+
+  private List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
+          String label, String description, boolean ignoreDescription)
+  {
     List<AlignmentAnnotation> result = new ArrayList<>();
     if (this.annotation != null)
     {
       for (AlignmentAnnotation ann : annotation)
       {
-        if (ann.calcId != null && ann.calcId.equals(calcId)
-                && ann.label != null && ann.label.equals(label))
+        if ((ann.calcId != null && ann.calcId.equals(calcId))
+                && (ann.label != null && ann.label.equals(label))
+                && ((ignoreDescription && description == null)
+                        || (ann.description != null
+                                && ann.description.equals(description))))
+
         {
           result.add(ann);
         }
@@ -1880,7 +1882,7 @@ private List<DBRefEntry> primaryRefs;
   }
 
   private List<DBRefEntry> tmpList;
-  
+
   @Override
   public List<DBRefEntry> getPrimaryDBRefs()
   {
@@ -1895,21 +1897,22 @@ private List<DBRefEntry> primaryRefs;
     synchronized (dbrefs)
     {
       if (refModCount == dbrefs.getModCount() && primaryRefs != null)
-         return primaryRefs;
       {
-        refModCount = dbrefs.getModCount(); 
-        DBRefUtils.ensurePrimaries(this);
-        primaryRefs = null;
+        return primaryRefs; // no changes
       }
-      List<DBRefEntry> primaries = (primaryRefs == null ? (primaryRefs = new ArrayList<>()) : primaryRefs);
+      refModCount = dbrefs.getModCount();
+      List<DBRefEntry> primaries = (primaryRefs == null
+              ? (primaryRefs = new ArrayList<>())
+              : primaryRefs);
       primaries.clear();
-      if (tmpList == null) {
-         tmpList = new ArrayList<>();
-         tmpList.add(null); // for replacement 
+      if (tmpList == null)
+      {
+        tmpList = new ArrayList<>();
+        tmpList.add(null); // for replacement
       }
       for (int i = 0, n = dbrefs.size(); i < n; i++)
       {
-       DBRefEntry ref = dbrefs.get(i);
+        DBRefEntry ref = dbrefs.get(i);
         if (!ref.isPrimaryCandidate())
         {
           continue;
@@ -1924,7 +1927,8 @@ private List<DBRefEntry> primaryRefs;
           }
         }
         // whilst it looks like it is a primary ref, we also sanity check type
-        if (DBRefSource.PDB_CANONICAL_NAME.equals(ref.getCanonicalSourceName()))
+        if (DBRefSource.PDB_CANONICAL_NAME
+                .equals(ref.getCanonicalSourceName()))
         {
           // PDB dbrefs imply there should be a PDBEntry associated
           // TODO: tighten PDB dbrefs
@@ -1937,17 +1941,24 @@ private List<DBRefEntry> primaryRefs;
           {
             continue;
           }
-        } else {
-             // check standard protein or dna sources
-             tmpList.set(0, ref);
-             List<DBRefEntry> res = DBRefUtils.selectDbRefs(!isProtein(), tmpList);
-             if (res == null || res.get(0) != tmpList.get(0))
-             {
-               continue;
-             }
-           }
+        }
+        else
+        {
+          // check standard protein or dna sources
+          tmpList.set(0, ref);
+          List<DBRefEntry> res = DBRefUtils.selectDbRefs(!isProtein(),
+                  tmpList);
+          if (res == null || res.get(0) != tmpList.get(0))
+          {
+            continue;
+          }
+        }
         primaries.add(ref);
       }
+
+      // version must be not null, as otherwise it will not be a candidate,
+      // above
+      DBRefUtils.ensurePrimaries(this, primaries);
       return primaries;
     }
   }
@@ -1965,15 +1976,6 @@ private List<DBRefEntry> primaryRefs;
 
     List<SequenceFeature> result = getFeatures().findFeatures(startPos,
             endPos, types);
-    if (datasetSequence != null)
-    {
-      result = datasetSequence.getFeatures().findFeatures(startPos, endPos,
-              types);
-    }
-    else
-    {
-      result = sequenceFeatureStore.findFeatures(startPos, endPos, types);
-    }
 
     /*
      * if end column is gapped, endPos may be to the right, 
@@ -2125,4 +2127,83 @@ private List<DBRefEntry> primaryRefs;
     // otherwise, sequence was completely hidden
     return 0;
   }
+
+  ////
+  //// Contact Matrix Holder Boilerplate
+  ////
+  ContactMapHolderI _cmholder = null;
+
+  private ContactMapHolderI getContactMapHolder()
+  {
+    if (datasetSequence!=null) {
+      return ((Sequence)datasetSequence).getContactMapHolder();
+    }
+    if (_cmholder==null)
+    {
+      _cmholder=new ContactMapHolder();
+    }
+    return _cmholder;
+  }
+  @Override
+  public Collection<ContactMatrixI> getContactMaps()
+  {
+    return getContactMapHolder().getContactMaps();
+  }
+
+  @Override
+  public ContactMatrixI getContactMatrixFor(AlignmentAnnotation ann)
+  {
+    return getContactMapHolder().getContactMatrixFor(ann);
+  }
+
+  @Override
+  public ContactListI getContactListFor(AlignmentAnnotation _aa, int column)
+  {
+    return getContactMapHolder().getContactListFor(_aa, column);
+  }
+
+  @Override
+  public AlignmentAnnotation addContactList(ContactMatrixI cm)
+  {
+    AlignmentAnnotation aa;
+    
+    if (datasetSequence != null)
+    {
+      aa = datasetSequence.addContactList(cm);
+      // clone the annotation for the local sequence
+      aa = new AlignmentAnnotation(aa);
+      aa.restrict(start, end);
+      aa.adjustForAlignment();
+      getContactMapHolder().addContactListFor(aa,cm);
+      addAlignmentAnnotation(aa);
+      return aa;
+    }
+    
+    // construct new annotation for matrix on dataset sequence
+    aa = getContactMapHolder().addContactList(cm);
+
+    Annotation _aa[] = new Annotation[getLength()];
+
+    for (int i = 0; i < _aa.length; _aa[i++] = new Annotation(0.0f))
+    {
+      ;
+    }
+    aa.annotations = _aa;
+    aa.setSequenceRef(this);
+    if (cm instanceof MappableContactMatrix
+            && !((MappableContactMatrix) cm).hasReferenceSeq())
+    {
+      ((MappableContactMatrix) cm).setRefSeq(this);
+    }
+    aa.createSequenceMapping(this, getStart(), false);
+    addAlignmentAnnotation(aa);
+    return aa;
+  }
+
+  @Override
+  public void addContactListFor(AlignmentAnnotation annotation,
+          ContactMatrixI cm)
+  {
+    getContactMapHolder().addContactListFor(annotation, cm);
+  }
 }