JAL-3692 parse DE for description, and other refactoring...
[jalview.git] / src / jalview / io / EmblFlatFile.java
index 9214f7e..5be4364 100644 (file)
@@ -3,6 +3,7 @@ package jalview.io;
 import java.io.IOException;
 import java.text.ParseException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -10,8 +11,8 @@ import java.util.Map.Entry;
 
 import jalview.bin.Cache;
 import jalview.datamodel.DBRefEntry;
-import jalview.datamodel.DBRefSource;
 import jalview.datamodel.FeatureProperties;
+import jalview.datamodel.Mapping;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
@@ -51,11 +52,11 @@ public class EmblFlatFile extends AlignFile // FileParse
 
     int codonStart = 1; // from CDS /codon_start
 
-    String proteinName; // from CDS /product; TODO: use for protein description
+    String proteinName; // from CDS /product; used for protein description
 
     String proteinId; // from CDS /protein_id
 
-    Map<String, String> cdsProps  = new Hashtable<>(); // CDS other qualifiers
+    Map<String, String> cdsProps = new Hashtable<>(); // CDS other qualifiers
   }
 
   private static final String WHITESPACE = "\\s+";
@@ -69,6 +70,8 @@ public class EmblFlatFile extends AlignFile // FileParse
 
   private String version; // from ID (second token)
 
+  private String description; // from (first) DE line
+
   private int length = 128; // from ID (7th token), with usable default
 
   private List<DBRefEntry> dbrefs; // from DR and also CDS /db_xref qualifiers
@@ -76,7 +79,7 @@ public class EmblFlatFile extends AlignFile // FileParse
   private String sequenceString; // from SQ lines
 
   private List<CdsData> cds;
-  
+
   /**
    * Constructor
    * 
@@ -107,6 +110,10 @@ public class EmblFlatFile extends AlignFile // FileParse
       {
         line = parseID(line);
       }
+      else if (line.startsWith("DE"))
+      {
+        line = parseDE(line);
+      }
       else if (line.startsWith("DR"))
       {
         line = parseDR(line);
@@ -180,6 +187,38 @@ public class EmblFlatFile extends AlignFile // FileParse
   }
 
   /**
+   * Reads sequence description from the first DE line found. Any trailing
+   * period is discarded. If there are multiple DE lines, only the first (short
+   * description) is read, the rest are ignored.
+   * 
+   * @param line
+   * @return
+   * @throws IOException
+   */
+  String parseDE(String line) throws IOException
+  {
+    String desc = line.substring(2).trim();
+    if (desc.endsWith("."))
+    {
+      desc = desc.substring(0, desc.length() - 1);
+    }
+    this.description = desc;
+
+    /*
+     * pass over any additional DE lines
+     */
+    while ((line = nextLine()) != null)
+    {
+      if (!line.startsWith("DE"))
+      {
+        break;
+      }
+    }
+
+    return line;
+  }
+
+  /**
    * Processes one DR line and saves as a DBRefEntry cross-reference. Returns
    * the line following the line processed.
    * 
@@ -208,7 +247,7 @@ public class EmblFlatFile extends AlignFile // FileParse
         if (!secondaryId.isEmpty())
         {
           // todo: is this right? secondary id is not a version number
-      //    version = secondaryId;
+          // version = secondaryId;
         }
       }
       this.dbrefs.add(new DBRefEntry(db, version, acc));
@@ -270,7 +309,7 @@ public class EmblFlatFile extends AlignFile // FileParse
     data.cdsLocation = tokens[2];
 
     line = nextLine();
-    while (line  != null)
+    while (line != null)
     {
       if (!line.startsWith("FT    ")) // 4 spaces
       {
@@ -348,7 +387,7 @@ public class EmblFlatFile extends AlignFile // FileParse
     }
 
     this.cds.add(data);
-    
+
     return line;
   }
 
@@ -358,7 +397,7 @@ public class EmblFlatFile extends AlignFile // FileParse
    * 
    * @param value
    *          the first line of the translation (likely quoted)
-   * @param data 
+   * @param data
    * @return
    * @throws IOException
    */
@@ -388,11 +427,63 @@ public class EmblFlatFile extends AlignFile // FileParse
     }
 
     data.translation = sb.toString();
-    
+
     return line;
   }
 
   /**
+   * Constructs and saves the sequence from parsed components
+   */
+  void assembleSequence()
+  {
+    String name = this.accession;
+    if (this.sourceDb != null)
+    {
+      name = this.sourceDb + "|" + name;
+    }
+    SequenceI seq = new Sequence(name, this.sequenceString);
+    seq.setDescription(this.description);
+
+    /*
+     * add a DBRef to itself
+     */
+    DBRefEntry selfRef = new DBRefEntry(sourceDb, version, accession);
+    int[] startEnd = new int[] { 1, seq.getLength() };
+    selfRef.setMap(new Mapping(null, startEnd, startEnd, 1, 1));
+    seq.addDBRef(selfRef);
+
+    for (DBRefEntry dbref : this.dbrefs)
+    {
+      seq.addDBRef(dbref);
+    }
+
+    processAllCDS(seq);
+
+    seq.deriveSequence();
+
+    addSequence(seq);
+  }
+
+  /**
+   * Process the CDS features, including generation of cross-references and
+   * mappings to the protein products (translation)
+   * 
+   * @param seq
+   */
+  protected void processAllCDS(SequenceI seq)
+  {
+    /*
+     * record protein products found to avoid duplication i.e. >1 CDS with 
+     * the same /protein_id [though not sure I can find an example of this]
+     */
+    Map<String, SequenceI> proteins = new HashMap<>();
+    for (CdsData data : cds)
+    {
+      processOneCDS(seq, data, proteins);
+    }
+  }
+
+  /**
    * Processes the parsed CDS feature data to
    * <ul>
    * <li>add a CDS feature to the sequence for each CDS start-end range</li>
@@ -403,8 +494,11 @@ public class EmblFlatFile extends AlignFile // FileParse
    * 
    * @param SequenceI
    *          dna
+   * @param proteins
+   *          map of protein products so far derived from CDS data
    */
-  void processCDS(SequenceI dna, CdsData data)
+  void processOneCDS(SequenceI dna, CdsData data,
+          Map<String, SequenceI> proteins)
   {
     /*
      * parse location into a list of [start, end, start, end] positions
@@ -437,54 +531,41 @@ public class EmblFlatFile extends AlignFile // FileParse
       sf.setValue(FeatureProperties.EXONPRODUCT, data.proteinName);
 
       dna.addSequenceFeature(sf);
-    }
-  }
-
-  /**
-   * Constructs a sequence for the protein product (if there is one), and dbrefs
-   * with mappings from dna to protein and the reverse
-   */
-  void processTranslation()
-  {
-    // TODO Auto-generated method stub
 
+      linkProteinProduct(dna, data, proteins);
+    }
   }
 
   /**
-   * Constructs and saves the sequence from parsed components
+   * Constructs a sequence for the protein product for the CDS data (if there is
+   * one), and dbrefs with mappings from CDS to protein and the reverse
+   * 
+   * @param dna
+   * @param data
+   * @param proteins
    */
-  void assembleSequence()
+  void linkProteinProduct(SequenceI dna, CdsData data, Map<String, SequenceI> proteins)
   {
-    String name = this.accession;
-    if (this.sourceDb != null)
+    /*
+     * check we have some data to work with
+     */
+    if (data.proteinId == null || data.translation == null)
     {
-      name = this.sourceDb + "|" + name;
+      return;
     }
-    SequenceI seq = new Sequence(name, this.sequenceString);
-    for (DBRefEntry dbref : this.dbrefs)
+    
+    /*
+     * Construct the protein sequence (if not already seen)
+     */
+    SequenceI protein = proteins.get(data.proteinId);
+    if (protein == null)
     {
-      seq.addDBRef(dbref);
+      protein = new Sequence(data.proteinId, data.translation, 1,
+              data.translation.length());
+      protein.setDescription(data.proteinName != null ? data.proteinName
+              : "Protein Product from " + sourceDb);
+      proteins.put(data.proteinId, protein);
     }
-
-    for (CdsData data : cds)
-    {
-      processCDS(seq, data);
-    };
-
-    processTranslation();
-
-    seq.deriveSequence();
-
-    addSequence(seq);
-  }
-
-  /**
-   * Output (print) is not implemented for EMBL flat file format
-   */
-  @Override
-  public String print(SequenceI[] seqs, boolean jvsuffix)
-  {
-    return null;
   }
 
   /**
@@ -514,4 +595,13 @@ public class EmblFlatFile extends AlignFile // FileParse
       return new int[] {};
     }
   }
+
+  /**
+   * Output (print) is not implemented for EMBL flat file format
+   */
+  @Override
+  public String print(SequenceI[] seqs, boolean jvsuffix)
+  {
+    return null;
+  }
 }