JAL-2110 JAL-2131 JAL-1765 remove reference to jalview.datamodel.Alignment from Align...
[jalview.git] / src / jalview / analysis / CrossRef.java
index 4e8f070..9fd87df 100644 (file)
@@ -163,7 +163,10 @@ public class CrossRef
     {
       for (DBRefEntry ref : xrefs)
       {
-        String source = ref.getSource();
+        /*
+         * avoid duplication e.g. ENSEMBL and Ensembl
+         */
+        String source = DBRefUtils.getCanonicalName(ref.getSource());
         if (!sources.contains(source))
         {
           sources.add(source);
@@ -173,19 +176,27 @@ public class CrossRef
   }
 
   /**
+   * Attempts to find cross-references from the sequences provided in the
+   * constructor to the given source database. Cross-references may be found
+   * <ul>
+   * <li>in dbrefs on the sequence which hold a mapping to a sequence
+   * <ul>
+   * <li>provided with a fetched sequence (e.g. ENA translation), or</li>
+   * <li>populated previously after getting cross-references</li>
+   * </ul>
+   * <li>as other sequences in the alignment which share a dbref identifier with
+   * the sequence</li>
+   * <li>by fetching from the remote database</li>
+   * </ul>
+   * The cross-referenced sequences, and mappings to them, are added to the
+   * alignment dataset.
    * 
-   * @param seqs
-   *          sequences whose xrefs are being retrieved
-   * @param dna
-   *          true if sequences are nucleotide
    * @param source
-   * @param al
-   *          alignment to search for cross-referenced sequences (and possibly
-   *          add to)
-   * @return products (as dataset sequences)
+   * @return cross-referenced sequences (as dataset sequences)
    */
   public Alignment findXrefSequences(String source)
   {
+
     List<SequenceI> rseqs = new ArrayList<SequenceI>();
     AlignedCodonFrame cf = new AlignedCodonFrame();
     SequenceIdMatcher matcher = new SequenceIdMatcher(
@@ -244,13 +255,20 @@ public class CrossRef
              * for example: UNIPROT {P0CE19, P0CE20} -> EMBL {J03321, X06707}
              */
             found = true;
-            SequenceI matchInDataset = findInDataset(mappedTo);// matcher.findIdMatch(mappedTo);
+            /*
+             * problem: matcher.findIdMatch() is lenient - returns a sequence
+             * with a dbref to the search arg e.g. ENST for ENSP - wrong
+             * but findInDataset() matches ENSP when looking for Uniprot...
+             */
+            SequenceI matchInDataset = findInDataset(xref);
+            /*matcher.findIdMatch(mappedTo);*/
             if (matchInDataset != null)
             {
               if (!rseqs.contains(matchInDataset))
               {
                 rseqs.add(matchInDataset);
               }
+              refIterator.remove();
               continue;
             }
             SequenceI rsq = new Sequence(mappedTo);
@@ -337,8 +355,11 @@ public class CrossRef
                   if (map.getTo() != null && map.getMap() != null)
                   {
                     // TODO findInDataset requires exact sequence match but
-                    // 'congruent' test only for the mapped part
-                    SequenceI matched = findInDataset(map.getTo());// matcher.findIdMatch(map.getTo());
+                    // 'congruent' test is only for the mapped part
+                    // maybe not a problem in practice since only ENA provide a
+                    // mapping and it is to the full protein translation of CDS
+                    SequenceI matched = findInDataset(dbref);
+                    // matcher.findIdMatch(map.getTo());
                     if (matched != null)
                     {
                       /*
@@ -379,15 +400,17 @@ public class CrossRef
                                 + " 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.
-                        // TODO don't we have to change the mapped to ranges
-                        // if not to the whole sequence?
                         map.setTo(dss);
+
+                        /*
+                         * give the reverse reference the inverse mapping 
+                         * (if it doesn't have one already)
+                         */
+                        setReverseMapping(dss, dbref, cf);
+
                         /*
                          * copy sequence features as well, avoiding
-                         * duplication (e.g. same variation from 2 
+                         * duplication (e.g. same variation from two 
                          * transcripts)
                          */
                         SequenceFeature[] sfs = ms.getSequenceFeatures();
@@ -397,7 +420,7 @@ public class CrossRef
                           {
                             /*
                              * make a flyweight feature object which ignores Parent
-                             * attribute in equality test, to avoid creating many
+                             * attribute in equality test; this avoids creating many
                              * otherwise duplicate exon features on genomic sequence
                              */
                             SequenceFeature newFeature = new SequenceFeature(
@@ -425,9 +448,9 @@ public class CrossRef
               }
             }
             retrievedSequence.updatePDBIds();
-            rseqs.add(retrievedSequence);
+            rseqs.add(retrievedDss);
             dataset.addSequence(retrievedDss);
-            matcher.add(retrievedSequence);
+            matcher.add(retrievedDss);
           }
         }
       }
@@ -437,33 +460,85 @@ public class CrossRef
     if (rseqs.size() > 0)
     {
       ral = new Alignment(rseqs.toArray(new SequenceI[rseqs.size()]));
-      if (cf != null && !cf.isEmpty())
+      if (!cf.isEmpty())
       {
-        ral.addCodonFrame(cf);
+        dataset.addCodonFrame(cf);
       }
     }
     return ral;
   }
 
   /**
+   * Sets the inverse sequence mapping in the corresponding dbref of the mapped
+   * to sequence (if any). This is used after fetching a cross-referenced
+   * sequence, if the fetched sequence has a mapping to the original sequence,
+   * to set the mapping in the original sequence's dbref.
+   * 
+   * @param mapFrom
+   *          the sequence mapped from
+   * @param dbref
+   * @param mappings
+   */
+  void setReverseMapping(SequenceI mapFrom, DBRefEntry dbref,
+          AlignedCodonFrame mappings)
+  {
+    SequenceI mapTo = dbref.getMap().getTo();
+    if (mapTo == null)
+    {
+      return;
+    }
+    DBRefEntry[] dbrefs = mapTo.getDBRefs();
+    if (dbrefs == null)
+    {
+      return;
+    }
+    for (DBRefEntry toRef : dbrefs)
+    {
+      if (toRef.hasMap() && mapFrom == toRef.getMap().getTo())
+      {
+        /*
+         * found the reverse dbref; update its mapping if null
+         */
+        if (toRef.getMap().getMap() == null)
+        {
+          MapList inverse = dbref.getMap().getMap().getInverse();
+          toRef.getMap().setMap(inverse);
+          mappings.addMap(mapTo, mapFrom, inverse);
+        }
+      }
+    }
+  }
+
+  /**
    * Returns the first identical sequence in the dataset if any, else null
    * 
-   * @param mappedTo
+   * @param xref
    * @return
    */
-  SequenceI findInDataset(SequenceI mappedTo)
+  SequenceI findInDataset(DBRefEntry xref)
   {
-    if (mappedTo == null)
+    if (xref == null || !xref.hasMap() || xref.getMap().getTo() == null)
     {
       return null;
     }
-    SequenceI dss = mappedTo.getDatasetSequence() == null ? mappedTo
-            : mappedTo.getDatasetSequence();
+    SequenceI mapsTo = xref.getMap().getTo();
+    String name = xref.getAccessionId();
+    String name2 = xref.getSource() + "|" + name;
+    SequenceI dss = mapsTo.getDatasetSequence() == null ? mapsTo : mapsTo
+            .getDatasetSequence();
     for (SequenceI seq : dataset.getSequences())
     {
-      if (sameSequence(seq, dss))
+      /*
+       * clumsy alternative to using SequenceIdMatcher which currently
+       * returns sequences with a dbref to the matched accession id 
+       * which we don't want
+       */
+      if (name.equals(seq.getName()) || seq.getName().startsWith(name2))
       {
-        return seq;
+        if (sameSequence(seq, dss))
+        {
+          return seq;
+        }
       }
     }
     return null;
@@ -544,9 +619,18 @@ public class CrossRef
   }
 
   /**
-   * Tries to make a mapping from dna to protein. If successful, adds the
-   * mapping to the dbref and the mappings collection and answers true,
-   * otherwise answers false.
+   * Tries to make a mapping between sequences. If successful, adds the mapping
+   * to the dbref and the mappings collection and answers true, otherwise
+   * answers false. The following methods of making are mapping are tried in
+   * turn:
+   * <ul>
+   * <li>if 'mapTo' holds a mapping to 'mapFrom', take the inverse; this is, for
+   * example, the case after fetching EMBL cross-references for a Uniprot
+   * sequence</li>
+   * <li>else check if the dna translates exactly to the protein (give or take
+   * start and stop codons></li>
+   * <li>else try to map based on CDS features on the dna sequence</li>
+   * </ul>
    * 
    * @param mapFrom
    * @param mapTo
@@ -558,6 +642,29 @@ public class CrossRef
           DBRefEntry xref, AlignedCodonFrame mappings)
   {
     MapList mapping = null;
+
+    /*
+     * look for a reverse mapping, if found make its inverse
+     */
+    if (mapTo.getDBRefs() != null)
+    {
+      for (DBRefEntry dbref : mapTo.getDBRefs())
+      {
+        String name = dbref.getSource() + "|" + dbref.getAccessionId();
+        if (dbref.hasMap() && mapFrom.getName().startsWith(name))
+        {
+          /*
+           * looks like we've found a map from 'mapTo' to 'mapFrom'
+           * - invert it to make the mapping the other way 
+           */
+          MapList reverse = dbref.getMap().getMap().getInverse();
+          xref.setMap(new Mapping(mapTo, reverse));
+          mappings.addMap(mapFrom, mapTo, reverse);
+          return true;
+        }
+      }
+    }
+
     if (fromDna)
     {
       mapping = AlignmentUtils.mapCdnaToProtein(mapTo, mapFrom);