JAL-1432 updated copyright notices
[jalview.git] / src / jalview / datamodel / xdb / embl / EmblEntry.java
index a22eff7..9661d12 100644 (file)
@@ -1,8 +1,27 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
+ * Copyright (C) 2014 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.datamodel.xdb.embl;
 
 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;
@@ -246,8 +265,8 @@ public class EmblEntry
    * EMBL Feature support is limited. The text below is included for the benefit
    * of any developer working on improving EMBL feature import in Jalview.
    * Extract from EMBL feature specification see
-   * http://www.embl-ebi.ac.uk/embl/Documentation/FT_definitions/feature_table.html
-   * 3.5 Location 3.5.1 Purpose
+   * http://www.embl-ebi.ac.uk/embl/Documentation
+   * /FT_definitions/feature_table.html 3.5 Location 3.5.1 Purpose
    * 
    * The location indicates the region of the presented sequence which
    * corresponds to a feature.
@@ -371,7 +390,6 @@ public class EmblEntry
    * 
    * join(1..100,J00194.1:100..202) Joins region 1..100 of the existing entry
    * with the region 100..202 of remote entry J00194
-   * 
    */
   /**
    * Recover annotated sequences from EMBL file
@@ -386,15 +404,23 @@ public class EmblEntry
    */
   public jalview.datamodel.SequenceI[] getSequences(boolean noNa,
           boolean noPeptide, String sourceDb)
-  {
+  { // TODO: ensure emblEntry.getSequences behaves correctly for returning all
+    // cases of noNa and noPeptide
     Vector seqs = new Vector();
     Sequence dna = null;
     if (!noNa)
     {
+      // In theory we still need to create this if noNa is set to avoid a null
+      // pointer exception
       dna = new Sequence(sourceDb + "|" + accession, sequence.getSequence());
       dna.setDescription(desc);
-      dna.addDBRef(new DBRefEntry(sourceDb, version, accession));
-      // TODO: add mapping for parentAccession attribute
+      DBRefEntry retrievedref = new DBRefEntry(sourceDb, version, accession);
+      dna.addDBRef(retrievedref);
+      // add map to indicate the sequence is a valid coordinate frame for the
+      // dbref
+      retrievedref.setMap(new Mapping(null, new int[]
+      { 1, dna.getLength() }, new int[]
+      { 1, dna.getLength() }, 1, 1));
       // TODO: transform EMBL Database refs to canonical form
       if (dbRefs != null)
         for (Iterator i = dbRefs.iterator(); i.hasNext(); dna
@@ -405,7 +431,6 @@ public class EmblEntry
     {
       for (Iterator i = features.iterator(); i.hasNext();)
       {
-        boolean nextFeature=false;
         EmblFeature feature = (EmblFeature) i.next();
         if (!noNa)
         {
@@ -416,133 +441,9 @@ public class EmblEntry
               ;
           }
         }
-        if (FeatureProperties.isCodingFeature(DBRefSource.EMBL, "CDS"))
+        if (FeatureProperties.isCodingFeature(sourceDb, feature.getName()))
         {
-          // extract coding region(s)
-          jalview.datamodel.Mapping map = null;
-          int[] exon = null;
-          if (feature.locations != null && feature.locations.size() > 0)
-          {
-            for (Enumeration locs = feature.locations.elements(); locs
-                    .hasMoreElements();)
-            {
-              EmblFeatureLocations loc = (EmblFeatureLocations) locs
-                      .nextElement();
-              int[] se = loc.getElementRanges(accession);
-              if (exon == null)
-              {
-                exon = se;
-              }
-              else
-              {
-                int[] t = new int[exon.length + se.length];
-                System.arraycopy(exon, 0, t, 0, exon.length);
-                System.arraycopy(se, 0, t, exon.length, se.length);
-                exon = t;
-              }
-            }
-          }
-          String prseq = null;
-          String prname = new String();
-          String prid = null;
-          Hashtable vals = new Hashtable();
-          int prstart = 1;
-          // get qualifiers
-          if (feature.getQualifiers() != null
-                  && feature.getQualifiers().size() > 0)
-          {
-            for (Iterator quals = feature.getQualifiers().iterator(); quals
-                    .hasNext();)
-            {
-              Qualifier q = (Qualifier) quals.next();
-              if (q.getName().equals("translation"))
-              {
-                prseq = q.getValues()[0];
-              }
-              else if (q.getName().equals("protein_id"))
-              {
-                prid = q.getValues()[0];
-              }
-              else if (q.getName().equals("codon_start"))
-              {
-                prstart = Integer.parseInt(q.getValues()[0]);
-              }
-              else if (q.getName().equals("product"))
-              {
-                prname = q.getValues()[0];
-              }
-              else
-              {
-                // throw anything else into the additional properties hash
-                vals.put(q.getName(), q.getValues().toString());
-              }
-            }
-          }
-          Sequence product = null;
-          if (prseq != null && prname != null && prid != null)
-          {
-            // extract proteins.
-            if (!noPeptide)
-            {
-              product = new Sequence(sourceDb + "|" + "EMBLCDS|" + prid
-                      + "|" + prname, prseq, prstart, prstart
-                      + prseq.length() - 1);
-              product.setDescription("Protein Product from " + sourceDb);
-              seqs.add(product);
-            }
-            // we have everything - create the mapping and perhaps the protein
-            // sequence
-            map = new jalview.datamodel.Mapping(product, exon, new int[]
-            { prstart, prstart + prseq.length() - 1 }, 3, 1);
-            // add cds feature to dna seq - this may include the stop codon
-            for (int xint = 0; xint < exon.length; xint += 2)
-            {
-              SequenceFeature sf = new SequenceFeature();
-              sf.setBegin(exon[xint]);
-              sf.setEnd(exon[xint + 1]);
-              sf.setType(feature.getName());
-              sf.setFeatureGroup(jalview.datamodel.DBRefSource.EMBL);
-              sf.setDescription("Exon " + (1 + xint) + " for protein '"
-                      + prname + "' EMBLCDS:" + prid);
-              sf.setValue(FeatureProperties.EXONPOS, new Integer(1+xint));
-              sf.setValue(FeatureProperties.EXONPRODUCT, prname);
-              if (vals != null && vals.size() > 0)
-              {
-                Enumeration kv = vals.elements();
-                while (kv.hasMoreElements())
-                {
-                  Object key = kv.nextElement();
-                  if (key != null)
-                    sf.setValue(key.toString(), vals.get(key));
-                }
-              }
-              dna.addSequenceFeature(sf);
-            }
-          }
-          // add dbRefs to sequence
-          if (feature.dbRefs != null && feature.dbRefs.size() > 0)
-          {
-            for (Iterator dbr = feature.dbRefs.iterator(); dbr.hasNext();)
-            {
-              DBRefEntry ref = (DBRefEntry) dbr.next();
-              ref.setSource(jalview.util.DBRefUtils.getCanonicalName(ref
-                      .getSource()));
-              // Hard code the kind of protein product accessions that EMBL cite
-              if (ref.getSource().equals(
-                      jalview.datamodel.DBRefSource.UNIPROT))
-              {
-                ref.setMap(map);
-              }
-              if (product != null)
-              {
-                DBRefEntry pref = new DBRefEntry(ref.getSource(), ref
-                        .getVersion(), ref.getAccessionId());
-                pref.setMap(null); // reference is direct
-              }
-              dna.addDBRef(ref);
-            }
-          }
-
+          parseCodingFeature(feature, sourceDb, seqs, dna, noPeptide);
         }
         else
         {
@@ -561,12 +462,13 @@ public class EmblEntry
     } catch (Exception e)
     {
       System.err.println("EMBL Record Features parsing error!");
-      System.err.println("Please report the following to help@jalview.org :");
-      System.err.println("EMBL Record "+accession);
-      System.err.println("Resulted in exception: "+e.getMessage());
+      System.err
+              .println("Please report the following to help@jalview.org :");
+      System.err.println("EMBL Record " + accession);
+      System.err.println("Resulted in exception: " + e.getMessage());
       e.printStackTrace(System.err);
     }
-    if (!noNa && dna!=null)
+    if (!noNa && dna != null)
     {
       seqs.add(dna);
     }
@@ -578,4 +480,345 @@ public class EmblEntry
     }
     return sqs;
   }
+
+  /**
+   * attempt to extract coding region and product from a feature and properly
+   * decorate it with annotations.
+   * 
+   * @param feature
+   *          coding feature
+   * @param sourceDb
+   *          source database for the EMBLXML
+   * @param seqs
+   *          place where sequences go
+   * @param dna
+   *          parent dna sequence for this record
+   * @param noPeptide
+   *          flag for generation of Peptide sequence objects
+   */
+  private void parseCodingFeature(EmblFeature feature, String sourceDb,
+          Vector seqs, Sequence dna, boolean noPeptide)
+  {
+    boolean isEmblCdna = sourceDb.equals(DBRefSource.EMBLCDS);
+    // extract coding region(s)
+    jalview.datamodel.Mapping map = null;
+    int[] exon = null;
+    if (feature.locations != null && feature.locations.size() > 0)
+    {
+      for (Enumeration locs = feature.locations.elements(); locs
+              .hasMoreElements();)
+      {
+        EmblFeatureLocations loc = (EmblFeatureLocations) locs
+                .nextElement();
+        int[] se = loc.getElementRanges(accession);
+        if (exon == null)
+        {
+          exon = se;
+        }
+        else
+        {
+          int[] t = new int[exon.length + se.length];
+          System.arraycopy(exon, 0, t, 0, exon.length);
+          System.arraycopy(se, 0, t, exon.length, se.length);
+          exon = t;
+        }
+      }
+    }
+    String prseq = null;
+    String prname = new String();
+    String prid = null;
+    Hashtable vals = new Hashtable();
+    int prstart = 1;
+    // get qualifiers
+    if (feature.getQualifiers() != null
+            && feature.getQualifiers().size() > 0)
+    {
+      for (Iterator quals = feature.getQualifiers().iterator(); quals
+              .hasNext();)
+      {
+        Qualifier q = (Qualifier) quals.next();
+        if (q.getName().equals("translation"))
+        {
+          StringBuffer prsq = new StringBuffer(q.getValues()[0]);
+          int p = prsq.indexOf(" ");
+          while (p > -1)
+          {
+            prsq.deleteCharAt(p);
+            p = prsq.indexOf(" ", p);
+          }
+          prseq = prsq.toString();
+          prsq = null;
+
+        }
+        else if (q.getName().equals("protein_id"))
+        {
+          prid = q.getValues()[0];
+        }
+        else if (q.getName().equals("codon_start"))
+        {
+          prstart = Integer.parseInt(q.getValues()[0]);
+        }
+        else if (q.getName().equals("product"))
+        {
+          prname = q.getValues()[0];
+        }
+        else
+        {
+          // throw anything else into the additional properties hash
+          String[] s = q.getValues();
+          StringBuffer sb = new StringBuffer();
+          if (s != null)
+          {
+            for (int i = 0; i < s.length; i++)
+            {
+              sb.append(s[i]);
+              sb.append("\n");
+            }
+          }
+          vals.put(q.getName(), sb.toString());
+        }
+      }
+    }
+    Sequence product = null;
+    exon = adjustForPrStart(prstart, exon);
+
+    if (prseq != null && prname != null && prid != null)
+    {
+      // extract proteins.
+      product = new Sequence(prid, prseq, 1, prseq.length());
+      product.setDescription(((prname.length() == 0) ? "Protein Product from "
+              + sourceDb
+              : prname));
+      if (!noPeptide)
+      {
+        // Protein is also added to vector of sequences returned
+        seqs.add(product);
+      }
+      // we have everything - create the mapping and perhaps the protein
+      // sequence
+      if (exon == null || exon.length == 0)
+      {
+        System.err
+                .println("Implementation Notice: EMBLCDS records not properly supported yet - Making up the CDNA region of this sequence... may be incorrect ("
+                        + sourceDb + ":" + getAccession() + ")");
+        if (prseq.length() * 3 == (1 - prstart + dna.getSequence().length))
+        {
+          System.err
+                  .println("Not allowing for additional stop codon at end of cDNA fragment... !");
+          // this might occur for CDS sequences where no features are
+          // marked.
+          exon = new int[]
+          { dna.getStart() + (prstart - 1), dna.getEnd() };
+          map = new jalview.datamodel.Mapping(product, exon, new int[]
+          { 1, prseq.length() }, 3, 1);
+        }
+        if ((prseq.length() + 1) * 3 == (1 - prstart + dna.getSequence().length))
+        {
+          System.err
+                  .println("Allowing for additional stop codon at end of cDNA fragment... will probably cause an error in VAMSAs!");
+          exon = new int[]
+          { dna.getStart() + (prstart - 1), dna.getEnd() - 3 };
+          map = new jalview.datamodel.Mapping(product, exon, new int[]
+          { 1, prseq.length() }, 3, 1);
+        }
+      }
+      else
+      {
+        // Trim the exon mapping if necessary - the given product may only be a
+        // fragment of a larger protein. (EMBL:AY043181 is an example)
+
+        if (isEmblCdna)
+        {
+          // TODO: Add a DbRef back to the parent EMBL sequence with the exon
+          // map
+          // if given a dataset reference, search dataset for parent EMBL
+          // sequence if it exists and set its map
+          // make a new feature annotating the coding contig
+        }
+        else
+        {
+          // final product length trunctation check
+
+          map = new jalview.datamodel.Mapping(product,
+                  adjustForProteinLength(prseq.length(), exon), new int[]
+                  { 1, prseq.length() }, 3, 1);
+          // reconstruct the EMBLCDS entry
+          // TODO: this is only necessary when there codon annotation is
+          // complete (I think JBPNote)
+          DBRefEntry pcdnaref = new DBRefEntry();
+          pcdnaref.setAccessionId(prid);
+          pcdnaref.setSource(DBRefSource.EMBLCDS);
+          pcdnaref.setVersion(getVersion()); // same as parent EMBL version.
+          jalview.util.MapList mp = new jalview.util.MapList(new int[]
+          { 1, prseq.length() }, new int[]
+          { 1 + (prstart - 1), (prstart - 1) + 3 * prseq.length() }, 1, 3);
+          // { 1 + (prstart - 1) * 3,
+          // 1 + (prstart - 1) * 3 + prseq.length() * 3 - 1 }, new int[]
+          // { 1prstart, prstart + prseq.length() - 1 }, 3, 1);
+          pcdnaref.setMap(new Mapping(mp));
+          if (product != null)
+            product.addDBRef(pcdnaref);
+
+        }
+      }
+      // add cds feature to dna seq - this may include the stop codon
+      for (int xint = 0; exon != null && xint < exon.length; xint += 2)
+      {
+        SequenceFeature sf = new SequenceFeature();
+        sf.setBegin(exon[xint]);
+        sf.setEnd(exon[xint + 1]);
+        sf.setType(feature.getName());
+        sf.setFeatureGroup(sourceDb);
+        sf.setDescription("Exon " + (1 + (int) (xint / 2))
+                + " for protein '" + prname + "' EMBLCDS:" + prid);
+        sf.setValue(FeatureProperties.EXONPOS, new Integer(1 + xint));
+        sf.setValue(FeatureProperties.EXONPRODUCT, prname);
+        if (vals != null && vals.size() > 0)
+        {
+          Enumeration kv = vals.elements();
+          while (kv.hasMoreElements())
+          {
+            Object key = kv.nextElement();
+            if (key != null)
+              sf.setValue(key.toString(), vals.get(key));
+          }
+        }
+        dna.addSequenceFeature(sf);
+      }
+    }
+    // add dbRefs to sequence
+    if (feature.dbRefs != null && feature.dbRefs.size() > 0)
+    {
+      for (Iterator dbr = feature.dbRefs.iterator(); dbr.hasNext();)
+      {
+        DBRefEntry ref = (DBRefEntry) dbr.next();
+        ref.setSource(jalview.util.DBRefUtils.getCanonicalName(ref
+                .getSource()));
+        // Hard code the kind of protein product accessions that EMBL cite
+        if (ref.getSource().equals(jalview.datamodel.DBRefSource.UNIPROT))
+        {
+          ref.setMap(map);
+          if (map != null && map.getTo() != null)
+          {
+            map.getTo().addDBRef(
+                    new DBRefEntry(ref.getSource(), ref.getVersion(), ref
+                            .getAccessionId())); // don't copy map over.
+            if (map.getTo().getName().indexOf(prid) == 0)
+            {
+              map.getTo().setName(
+                      jalview.datamodel.DBRefSource.UNIPROT + "|"
+                              + ref.getAccessionId());
+            }
+          }
+        }
+        if (product != null)
+        {
+          DBRefEntry pref = new DBRefEntry(ref.getSource(),
+                  ref.getVersion(), ref.getAccessionId());
+          pref.setMap(null); // reference is direct
+          product.addDBRef(pref);
+          // Add converse mapping reference
+          if (map != null)
+          {
+            Mapping pmap = new Mapping(dna, map.getMap().getInverse());
+            pref = new DBRefEntry(sourceDb, getVersion(),
+                    this.getAccession());
+            pref.setMap(pmap);
+            if (map.getTo() != null)
+            {
+              map.getTo().addDBRef(pref);
+            }
+          }
+        }
+        dna.addDBRef(ref);
+      }
+    }
+  }
+
+  private int[] adjustForPrStart(int prstart, int[] exon)
+  {
+
+    int origxon[], sxpos = -1;
+    int sxstart, sxstop; // unnecessary variables used for debugging
+    // first adjust range for codon start attribute
+    if (prstart > 1)
+    {
+      origxon = new int[exon.length];
+      System.arraycopy(exon, 0, origxon, 0, exon.length);
+      int cdspos = 0;
+      for (int x = 0; x < exon.length && sxpos == -1; x += 2)
+      {
+        cdspos += exon[x + 1] - exon[x] + 1;
+        if (prstart <= cdspos)
+        {
+          sxpos = x;
+          sxstart = exon[x];
+          sxstop = exon[x + 1];
+          // and adjust start boundary of first exon.
+          exon[x] = exon[x + 1] - cdspos + prstart;
+          break;
+        }
+      }
+
+      if (sxpos > 0)
+      {
+        int[] nxon = new int[exon.length - sxpos];
+        System.arraycopy(exon, sxpos, nxon, 0, exon.length - sxpos);
+        exon = nxon;
+      }
+    }
+    return exon;
+  }
+
+  /**
+   * truncate the last exon interval to the prlength'th codon
+   * 
+   * @param prlength
+   * @param exon
+   * @return new exon
+   */
+  private int[] adjustForProteinLength(int prlength, int[] exon)
+  {
+
+    int origxon[], sxpos = -1, endxon = 0, cdslength = prlength * 3;
+    int sxstart, sxstop; // unnecessary variables used for debugging
+    // first adjust range for codon start attribute
+    if (prlength >= 1 && exon != null)
+    {
+      origxon = new int[exon.length];
+      System.arraycopy(exon, 0, origxon, 0, exon.length);
+      int cdspos = 0;
+      for (int x = 0; x < exon.length && sxpos == -1; x += 2)
+      {
+        cdspos += exon[x + 1] - exon[x] + 1;
+        if (cdslength <= cdspos)
+        {
+          // advanced beyond last codon.
+          sxpos = x;
+          sxstart = exon[x];
+          sxstop = exon[x + 1];
+          if (cdslength != cdspos)
+          {
+            System.err
+                    .println("Truncating final exon interval on region by "
+                            + (cdspos - cdslength));
+          }
+          // locate the new end boundary of final exon as endxon
+          endxon = exon[x + 1] - cdspos + cdslength;
+          break;
+        }
+      }
+
+      if (sxpos != -1)
+      {
+        // and trim the exon interval set if necessary
+        int[] nxon = new int[sxpos + 2];
+        System.arraycopy(exon, 0, nxon, 0, sxpos + 2);
+        nxon[sxpos + 1] = endxon; // update the end boundary for the new exon
+                                  // set
+        exon = nxon;
+      }
+    }
+    return exon;
+  }
 }