JAL-3365 expand range of allowed DSSP secondary structure symbols in Stockholm files
[jalview.git] / src / jalview / io / StockholmFile.java
index 84e629e..5d645ca 100644 (file)
  */
 package jalview.io;
 
-import jalview.analysis.Rna;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.Annotation;
-import jalview.datamodel.DBRefEntry;
-import jalview.datamodel.Mapping;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceFeature;
-import jalview.datamodel.SequenceI;
-import jalview.schemes.ResidueProperties;
-import jalview.util.Comparison;
-import jalview.util.Format;
-import jalview.util.MessageManager;
-
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
@@ -45,6 +31,7 @@ import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Vector;
 
@@ -53,8 +40,21 @@ import com.stevesoft.pat.Regex;
 import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses;
 import fr.orsay.lri.varna.factories.RNAFactory;
 import fr.orsay.lri.varna.models.rna.RNA;
-
-// import org.apache.log4j.*;
+import jalview.analysis.Rna;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+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.schemes.ResidueProperties;
+import jalview.util.Comparison;
+import jalview.util.DBRefUtils;
+import jalview.util.Format;
+import jalview.util.MessageManager;
 
 /**
  * This class is supposed to parse a Stockholm format file into Jalview There
@@ -76,20 +76,21 @@ public class StockholmFile extends AlignFile
 {
   private static final String ANNOTATION = "annotation";
 
-//  private static final Regex OPEN_PAREN = new Regex("(<|\\[)", "(");
-//
-//  private static final Regex CLOSE_PAREN = new Regex("(>|\\])", ")");
+  // private static final Regex OPEN_PAREN = new Regex("(<|\\[)", "(");
+  //
+  // private static final Regex CLOSE_PAREN = new Regex("(>|\\])", ")");
 
   public static final Regex DETECT_BRACKETS = new Regex(
           "(<|>|\\[|\\]|\\(|\\)|\\{|\\})");
 
-  // WUSS extended symbols. Avoid ambiguity with protein SS annotations by using NOT_RNASS first.
+  // WUSS extended symbols. Avoid ambiguity with protein SS annotations by using
+  // NOT_RNASS first.
   public static final String RNASS_BRACKETS = "<>[](){}AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
 
   // use the following regex to decide an annotations (whole) line is NOT an RNA
   // SS (it contains only E,H,e,h and other non-brace/non-alpha chars)
   private static final Regex NOT_RNASS = new Regex(
-          "^[^<>[\\](){}A-DF-Za-df-z]*$");
+          "^[^<>[\\](){}ADFJ-RUVWYZadfj-ruvwyz]*$");
 
   StringBuffer out; // output buffer
 
@@ -239,8 +240,8 @@ public class StockholmFile extends AlignFile
     Regex openparen = new Regex("(<|\\[)", "(");
     Regex closeparen = new Regex("(>|\\])", ")");
 
-//    // Detect if file is RNA by looking for bracket types
-//    Regex detectbrackets = new Regex("(<|>|\\[|\\]|\\(|\\))");
+    // // Detect if file is RNA by looking for bracket types
+    // Regex detectbrackets = new Regex("(<|>|\\[|\\]|\\(|\\))");
 
     rend.optimize();
     p.optimize();
@@ -332,17 +333,15 @@ public class StockholmFile extends AlignFile
 
           if (accAnnotations != null && accAnnotations.containsKey("AC"))
           {
-            if (dbsource != null)
+            String dbr = (String) accAnnotations.get("AC");
+            if (dbr != null)
             {
-              String dbr = (String) accAnnotations.get("AC");
-              if (dbr != null)
-              {
-                // we could get very clever here - but for now - just try to
-                // guess accession type from source of alignment plus structure
-                // of accession
-                guessDatabaseFor(seqO, dbr, dbsource);
-
-              }
+              // we could get very clever here - but for now - just try to
+              // guess accession type from type of sequence, source of alignment
+              // plus
+              // structure
+              // of accession
+              guessDatabaseFor(seqO, dbr, dbsource);
             }
             // else - do what ? add the data anyway and prompt the user to
             // specify what references these are ?
@@ -527,6 +526,11 @@ public class StockholmFile extends AlignFile
               treeName = an.stringMatched(2);
               treeString = new StringBuffer();
             }
+            // TODO: JAL-3532 - this is where GF comments and database
+            // references are lost
+            // suggest overriding this method for Stockholm files to catch and
+            // properly
+            // process CC, DR etc into multivalued properties
             setAlignmentProperty(an.stringMatched(1), an.stringMatched(2));
           }
         }
@@ -755,6 +759,13 @@ public class StockholmFile extends AlignFile
         st = -1;
       }
     }
+    if (dbsource == null)
+    {
+      // make up an origin based on whether the sequence looks like it is
+      // nucleotide
+      // or protein
+      dbsource = (seqO.isProtein()) ? "PFAM" : "RFAM";
+    }
     if (dbsource.equals("PFAM"))
     {
       seqdb = "UNIPROT";
@@ -930,6 +941,12 @@ public class StockholmFile extends AlignFile
     return annot;
   }
 
+  private String dbref_to_ac_record(DBRefEntry ref)
+  {
+    return ref.getSource().toString() + " ; "
+            + ref.getAccessionId().toString();
+  }
+
   @Override
   public String print(SequenceI[] s, boolean jvSuffix)
   {
@@ -944,6 +961,7 @@ public class StockholmFile extends AlignFile
     int slen = s.length;
     SequenceI seq;
     Hashtable<String, String> dataRef = null;
+    boolean isAA = s[in].isProtein();
     while ((in < slen) && ((seq = s[in]) != null))
     {
       String tmp = printId(seq, jvSuffix);
@@ -961,14 +979,29 @@ public class StockholmFile extends AlignFile
         {
           dataRef = new Hashtable<>();
         }
-        for (int idb = 0; idb < ndb; idb++)
+        List<DBRefEntry> primrefs = seq.getPrimaryDBRefs();
+        if (primrefs.size() >= 1)
         {
-
-          DBRefEntry ref = seqrefs.get(idb);
-          String datAs1 = ref.getSource().toString()
-                  + " ; "
-                  + ref.getAccessionId().toString();
-          dataRef.put(tmp, datAs1);
+          dataRef.put(tmp, dbref_to_ac_record(primrefs.get(0)));
+        }
+        else
+        {
+          for (int idb = 0; idb < seq.getDBRefs().size(); idb++)
+          {
+            DBRefEntry dbref = seq.getDBRefs().get(idb);
+            dataRef.put(tmp, dbref_to_ac_record(dbref));
+            // if we put in a uniprot or EMBL record then we're done:
+            if (isAA && DBRefSource.UNIPROT
+                    .equals(DBRefUtils.getCanonicalName(dbref.getSource())))
+            {
+              break;
+            }
+            if (!isAA && DBRefSource.EMBL
+                    .equals(DBRefUtils.getCanonicalName(dbref.getSource())))
+            {
+              break;
+            }
+          }
         }
       }
       in++;
@@ -998,10 +1031,11 @@ public class StockholmFile extends AlignFile
       while (en.hasMoreElements())
       {
         Object idd = en.nextElement();
-        String type = (String) dataRef.remove(idd);
+        String type = dataRef.remove(idd);
         out.append(new Format("%-" + (maxid - 2) + "s")
                 .form("#=GS " + idd.toString() + " "));
-        if (type.contains("PFAM") || type.contains("RFAM"))
+        if (isAA && type.contains("UNIPROT")
+                || (!isAA && type.contains("EMBL")))
         {
 
           out.append(" AC " + type.substring(type.indexOf(";") + 1));
@@ -1081,7 +1115,7 @@ public class StockholmFile extends AlignFile
         }
         else
         {
-          key = type2id(aa.label.toLowerCase());
+          key = type2id(aa.label.toLowerCase(Locale.ROOT));
           if (key == null)
           {
             label = aa.label;