JAL-1705 various refactoring towards Uniprot-to-Ensembl fetching
[jalview.git] / src / jalview / ext / ensembl / EnsemblGene.java
index 1325bec..73649b4 100644 (file)
@@ -4,13 +4,17 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
-import jalview.io.gff.SequenceOntology;
+import jalview.io.gff.SequenceOntologyFactory;
+import jalview.io.gff.SequenceOntologyI;
 import jalview.util.MapList;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import com.stevesoft.pat.Regex;
+
 /**
  * A class that fetches genomic sequence and all transcripts for an Ensembl gene
  * 
@@ -18,6 +22,12 @@ import java.util.List;
  */
 public class EnsemblGene extends EnsemblSeqProxy
 {
+  private static final String GENE_PREFIX = "gene:";
+
+  // TODO modify to accept other species e.g. ENSMUSGnnn
+  private static final Regex ACCESSION_REGEX = new Regex(
+          "(ENSG|ENST)[0-9]{11}$");
+
   private static final EnsemblFeatureType[] FEATURES_TO_FETCH = {
       EnsemblFeatureType.gene, EnsemblFeatureType.transcript,
       EnsemblFeatureType.exon, EnsemblFeatureType.cds,
@@ -59,31 +69,97 @@ public class EnsemblGene extends EnsemblSeqProxy
   @Override
   public AlignmentI getSequenceRecords(String query) throws Exception
   {
-    // TODO ? if an ENST identifier is supplied, convert to ENSG?
+    List<String> transcriptsWanted = null;
+
+    if (isTranscriptIdentifier(query))
+    {
+      transcriptsWanted = Arrays.asList(query
+              .split(getAccessionSeparator()));
+      query = getGeneForTranscript(query);
+      if (query == null)
+      {
+        return null;
+      }
+    }
+
     AlignmentI al = super.getSequenceRecords(query);
     if (al.getHeight() > 0)
     {
-      getTranscripts(al, query);
+      getTranscripts(al, query, transcriptsWanted);
     }
 
     return al;
   }
 
   /**
+   * Gets the parent gene identifier for a given transcript identifier, by
+   * retrieving 'transcript' features overlapping the transcript, and finding
+   * the Parent property of the feature whose id is the given identifier.
+   * 
+   * @param query
+   * @return
+   */
+  protected String getGeneForTranscript(String transcriptId)
+  {
+    String geneId = null;
+
+    /*
+     * reduce multiple transcripts (e.g. from Uniprot x-ref) to the first
+     * one only as representative (they should all have the same gene)
+     */
+    transcriptId = transcriptId.split(getAccessionSeparator())[0];
+
+    try
+    {
+      EnsemblFeatureType[] geneFeature = new EnsemblFeatureType[] { EnsemblFeatureType.transcript };
+      AlignmentI al = new EnsemblFeatures().getSequenceRecords(
+              transcriptId, geneFeature);
+      if (al != null && al.getHeight() > 0)
+      {
+        SequenceFeature[] sfs = al.getSequenceAt(0).getSequenceFeatures();
+        if (sfs != null)
+        {
+          for (SequenceFeature sf : sfs)
+          {
+            if (transcriptId.equals(getTranscriptId(sf)))
+            {
+              String parent = (String) sf.getValue(PARENT);
+              if (parent != null && parent.startsWith(GENE_PREFIX))
+              {
+                geneId = parent.substring(5);
+              }
+              break;
+            }
+          }
+        }
+      }
+      return geneId;
+    } catch (IOException e)
+    {
+      System.err.println("Error retrieving gene id for " + transcriptId
+              + ": " + e.getMessage());
+      return null;
+    }
+  }
+
+  /**
    * Constructs all transcripts for the gene, as identified by "transcript"
    * features whose Parent is the requested gene. The coding transcript
    * sequences (i.e. with introns omitted) are added to the alignment.
    * 
    * @param al
    * @param accId
+   * @param transcriptsWanted
+   *          optional list of transcript ids to filter by
    * @throws Exception
    */
-  protected void getTranscripts(AlignmentI al, String accId)
+  protected void getTranscripts(AlignmentI al, String accId,
+          List<String> transcriptsWanted)
           throws Exception
   {
     SequenceI gene = al.getSequenceAt(0);
     List<SequenceFeature> transcriptFeatures = getTranscriptFeatures(accId,
-            gene);
+            gene, transcriptsWanted);
 
     for (SequenceFeature transcriptFeature : transcriptFeatures)
     {
@@ -107,7 +183,7 @@ public class EnsemblGene extends EnsemblSeqProxy
   SequenceI makeTranscript(SequenceFeature transcriptFeature,
           AlignmentI al, SequenceI gene)
   {
-    String accId = (String) transcriptFeature.getValue("transcript_id");
+    String accId = getTranscriptId(transcriptFeature);
     if (accId == null)
     {
       return null;
@@ -132,10 +208,10 @@ public class EnsemblGene extends EnsemblSeqProxy
      */
     String parentId = "transcript:" + accId;
     List<SequenceFeature> splices = findFeatures(gene,
-            SequenceOntology.EXON, parentId);
+            SequenceOntologyI.EXON, parentId);
     if (splices.isEmpty())
     {
-      splices = findFeatures(gene, SequenceOntology.CDS, parentId);
+      splices = findFeatures(gene, SequenceOntologyI.CDS, parentId);
     }
 
     int transcriptLength = 0;
@@ -176,25 +252,38 @@ public class EnsemblGene extends EnsemblSeqProxy
     /*
      * and finally fetch the protein product and save as a cross-reference
      */
-    addProteinProduct(transcript);
+    new EnsemblCdna().addProteinProduct(transcript);
 
     return transcript;
   }
 
   /**
+   * Returns the 'transcript_id' property of the sequence feature (or null)
+   * 
+   * @param feature
+   * @return
+   */
+  protected String getTranscriptId(SequenceFeature feature)
+  {
+    return (String) feature.getValue("transcript_id");
+  }
+
+  /**
    * Returns a list of the transcript features on the sequence whose Parent is
    * the gene for the accession id.
    * 
    * @param accId
    * @param geneSequence
+   * @param transcriptsWanted
+   *          optional list of ids to filter on
    * @return
    */
   protected List<SequenceFeature> getTranscriptFeatures(String accId,
-          SequenceI geneSequence)
+          SequenceI geneSequence, List<String> transcriptsWanted)
   {
     List<SequenceFeature> transcriptFeatures = new ArrayList<SequenceFeature>();
 
-    String parentIdentifier = "gene:" + accId;
+    String parentIdentifier = GENE_PREFIX + accId;
     SequenceFeature[] sfs = geneSequence.getSequenceFeatures();
 
     if (sfs != null)
@@ -203,6 +292,14 @@ public class EnsemblGene extends EnsemblSeqProxy
       {
         if (isTranscript(sf.getType()))
         {
+          if (transcriptsWanted != null)
+          {
+            String transcriptId = (String) sf.getValue("transcript_id");
+            if (!transcriptsWanted.contains(transcriptId))
+            {
+              // continue;
+            }
+          }
           String parent = (String) sf.getValue(PARENT);
           if (parentIdentifier.equals(parent))
           {
@@ -218,11 +315,11 @@ public class EnsemblGene extends EnsemblSeqProxy
   @Override
   public String getDescription()
   {
-    return "Fetches all transcripts and variant features for a gene";
+    return "Fetches all transcripts and variant features for a gene or transcript";
   }
 
   /**
-   * Default test query is a transcript
+   * Default test query is a gene id (can also enter a transcript id)
    */
   @Override
   public String getTestQuery()
@@ -240,11 +337,11 @@ public class EnsemblGene extends EnsemblSeqProxy
   @Override
   protected boolean identifiesSequence(SequenceFeature sf, String accId)
   {
-    if (SequenceOntology.getInstance().isA(sf.getType(),
-            SequenceOntology.GENE))
+    if (SequenceOntologyFactory.getInstance().isA(sf.getType(),
+            SequenceOntologyI.GENE))
     {
       String id = (String) sf.getValue(ID);
-      if (("gene:" + accId).equals(id))
+      if ((GENE_PREFIX + accId).equals(id))
       {
         return true;
       }
@@ -262,8 +359,8 @@ public class EnsemblGene extends EnsemblSeqProxy
   @Override
   protected boolean retainFeature(SequenceFeature sf, String accessionId)
   {
-    if (SequenceOntology.getInstance().isA(sf.getType(),
-            SequenceOntology.GENE))
+    if (SequenceOntologyFactory.getInstance().isA(sf.getType(),
+            SequenceOntologyI.GENE))
     {
       return false;
     }
@@ -271,7 +368,7 @@ public class EnsemblGene extends EnsemblSeqProxy
     if (isTranscript(sf.getType()))
     {
       String parent = (String) sf.getValue(PARENT);
-      if (!("gene:" + accessionId).equals(parent))
+      if (!(GENE_PREFIX + accessionId).equals(parent))
       {
         return false;
       }
@@ -299,4 +396,19 @@ public class EnsemblGene extends EnsemblSeqProxy
     return super.getCrossReferenceDatabases();
   }
 
+  /**
+   * Override to do nothing as Ensembl doesn't return a protein sequence for a
+   * gene identifier
+   */
+  @Override
+  protected void addProteinProduct(SequenceI querySeq)
+  {
+  }
+
+  @Override
+  public Regex getAccessionValidator()
+  {
+    return ACCESSION_REGEX;
+  }
+
 }