JAL-1705 reworked AlignmentUtils.makeCdsAlignment and associated code
[jalview.git] / src / jalview / analysis / CrossRef.java
index 21fd08d..3563eba 100644 (file)
@@ -27,8 +27,10 @@ import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.DBRefSource;
 import jalview.datamodel.Mapping;
 import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.util.DBRefUtils;
+import jalview.util.MapList;
 import jalview.ws.SequenceFetcher;
 import jalview.ws.seqfetcher.ASequenceFetcher;
 
@@ -45,6 +47,27 @@ import java.util.Vector;
  */
 public class CrossRef
 {
+  /*
+   * A sub-class that ignores Parent attribute when comparing sequence 
+   * features. This avoids 'duplicate' CDS features that only
+   * differ in their parent Transcript ids.
+   */
+  class MySequenceFeature extends SequenceFeature
+  {
+    private SequenceFeature feat;
+
+    MySequenceFeature(SequenceFeature sf)
+    {
+      this.feat = sf;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+      return feat.equals(o, true);
+    }
+  }
+
   /**
    * Select just the DNA or protein references for a protein or dna sequence
    * 
@@ -197,30 +220,20 @@ public class CrossRef
 
   /**
    * 
-   * @param dna
-   * @param seqs
-   * @return
-   */
-  public static Alignment findXrefSequences(SequenceI[] seqs, boolean dna,
-          String source)
-  {
-    return findXrefSequences(seqs, dna, source, null);
-  }
-
-  /**
-   * 
    * @param seqs
    *          sequences whose xrefs are being retrieved
    * @param dna
    *          true if sequences are nucleotide
    * @param source
-   * @param dataset
-   *          alignment to search for product sequences.
+   * @param al
+   *          alignment to search for cross-referenced sequences (and possibly
+   *          add to)
    * @return products (as dataset sequences)
    */
-  public static Alignment findXrefSequences(SequenceI[] seqs, boolean dna,
-          String source, AlignmentI dataset)
+  public static Alignment findXrefSequences(SequenceI[] seqs,
+          final boolean dna, final String source, AlignmentI al)
   {
+    AlignmentI dataset = al.getDataset() == null ? al : al.getDataset();
     List<SequenceI> rseqs = new ArrayList<SequenceI>();
     AlignedCodonFrame cf = new AlignedCodonFrame();
     for (SequenceI seq : seqs)
@@ -279,7 +292,8 @@ public class CrossRef
           // xrefs on this sequence.
           if (dataset != null)
           {
-            found |= searchDataset(dss, xref, dataset, rseqs, cf); // ,false,!dna);
+            found |= searchDataset(dss, xref, dataset, rseqs, cf, false,
+                    !dna);
             if (found)
             {
               xrfs[r] = null; // we've recovered seqs for this one.
@@ -337,8 +351,15 @@ public class CrossRef
                               + seq.getName());
               e.printStackTrace();
             }
+
             if (retrieved != null)
             {
+              updateDbrefMappings(dna, seq, xrfs, retrieved, cf);
+
+              SequenceIdMatcher matcher = new SequenceIdMatcher(
+                      dataset.getSequences());
+              List<SequenceFeature> copiedFeatures = new ArrayList<SequenceFeature>();
+              CrossRef me = new CrossRef();
               for (int rs = 0; rs < retrieved.length; rs++)
               {
                 // TODO: examine each sequence for 'redundancy'
@@ -354,8 +375,25 @@ public class CrossRef
                     {
                       if (map.getTo() != null && map.getMap() != null)
                       {
-                        // should search the local dataset to find any existing
-                        // candidates for To !
+                        SequenceI matched = matcher
+                                .findIdMatch(map.getTo());
+                        if (matched != null)
+                        {
+                          /*
+                           * already got an xref to this sequence; update this
+                           * map to point to the same sequence, and add
+                           * any new dbrefs to it
+                           */
+                          for (DBRefEntry ref : map.getTo().getDBRefs())
+                          {
+                            matched.addDBRef(ref); // add or update mapping
+                          }
+                          map.setTo(matched);
+                        }
+                        else
+                        {
+                          matcher.add(map.getTo());
+                        }
                         try
                         {
                           // compare ms with dss and replace with dss in mapping
@@ -364,17 +402,54 @@ public class CrossRef
                           int sf = map.getMap().getToLowest();
                           int st = map.getMap().getToHighest();
                           SequenceI mappedrg = ms.getSubSequence(sf, st);
-                          SequenceI loc = dss.getSubSequence(sf, st);
+                          // SequenceI loc = dss.getSubSequence(sf, st);
                           if (mappedrg.getLength() > 0
-                                  && mappedrg.getSequenceAsString().equals(
-                                          loc.getSequenceAsString()))
+                                  && ms.getSequenceAsString().equals(
+                                          dss.getSequenceAsString()))
+                          // && mappedrg.getSequenceAsString().equals(
+                          // loc.getSequenceAsString()))
                           {
-                            System.err
-                                    .println("Mapping updated for retrieved crossreference");
+                            String msg = "Mapping updated from "
+                                    + ms.getName()
+                                    + " to retrieved crossreference "
+                                    + dss.getName();
+                            System.out.println(msg);
                             // method to update all refs of existing To on
                             // retrieved sequence with dss and merge any props
                             // on To onto dss.
                             map.setTo(dss);
+                            /*
+                             * copy sequence features as well, avoiding
+                             * duplication (e.g. same variation from 2 
+                             * transcripts)
+                             */
+                            SequenceFeature[] sfs = ms
+                                    .getSequenceFeatures();
+                            if (sfs != null)
+                            {
+                              for (SequenceFeature feat : sfs)
+                              {
+                                /* 
+                                 * we override SequenceFeature.equals here (but
+                                 * not elsewhere) to ignore Parent attribute
+                                 * TODO not quite working yet!
+                                 */
+                                if (!copiedFeatures
+                                        .contains(me.new MySequenceFeature(
+                                                feat)))
+                                {
+                                  dss.addSequenceFeature(feat);
+                                  copiedFeatures.add(feat);
+                                }
+                              }
+                            }
+                            cf.addMap(retrieved[rs].getDatasetSequence(),
+                                    dss, map.getMap());
+                          }
+                          else
+                          {
+                            cf.addMap(retrieved[rs].getDatasetSequence(),
+                                    map.getTo(), map.getMap());
                           }
                         } catch (Exception e)
                         {
@@ -398,9 +473,7 @@ public class CrossRef
     Alignment ral = null;
     if (rseqs.size() > 0)
     {
-      SequenceI[] rsqs = new SequenceI[rseqs.size()];
-      rseqs.toArray(rsqs);
-      ral = new Alignment(rsqs);
+      ral = new Alignment(rseqs.toArray(new SequenceI[rseqs.size()]));
       if (cf != null && !cf.isEmpty())
       {
         ral.addCodonFrame(cf);
@@ -410,6 +483,69 @@ public class CrossRef
   }
 
   /**
+   * Updates any empty mappings in the cross-references with one to a compatible
+   * retrieved sequence if found, and adds any new mappings to the
+   * AlignedCodonFrame
+   * 
+   * @param dna
+   * @param mapFrom
+   * @param xrefs
+   * @param retrieved
+   * @param acf
+   */
+  static void updateDbrefMappings(boolean dna, SequenceI mapFrom,
+          DBRefEntry[] xrefs, SequenceI[] retrieved, AlignedCodonFrame acf)
+  {
+    SequenceIdMatcher matcher = new SequenceIdMatcher(retrieved);
+    for (DBRefEntry xref : xrefs)
+    {
+      if (!xref.hasMap())
+      {
+        String targetSeqName = xref.getSource() + "|"
+                + xref.getAccessionId();
+        SequenceI[] matches = matcher.findAllIdMatches(targetSeqName);
+        if (matches == null)
+        {
+          return;
+        }
+        for (SequenceI seq : matches)
+        {
+          MapList mapping = null;
+          if (dna)
+          {
+            mapping = AlignmentUtils.mapCdnaToProtein(seq, mapFrom);
+          }
+          else
+          {
+            mapping = AlignmentUtils.mapCdnaToProtein(mapFrom, seq);
+            if (mapping != null)
+            {
+              mapping = mapping.getInverse();
+            }
+          }
+          if (mapping != null)
+          {
+            xref.setMap(new Mapping(seq, mapping));
+            if (dna)
+            {
+              AlignmentUtils.computeProteinFeatures(mapFrom, seq, mapping);
+            }
+            if (dna)
+            {
+              acf.addMap(mapFrom, seq, mapping);
+            }
+            else
+            {
+              acf.addMap(seq, mapFrom, mapping.getInverse());
+            }
+            continue;
+          }
+        }
+      }
+    }
+  }
+
+  /**
    * find references to lrfs in the cross-reference set of each sequence in
    * dataset (that is not equal to sequenceI) Identifies matching DBRefEntry
    * based on source and accession string only - Map and Version are nulled.