Merge branch 'spike/JAL-2040_JAL-2137_phyre2' into features/JAL-2040_JAL-2137_phyre2
[jalview.git] / src / jalview / io / AnnotationFile.java
index 7702792..eca27a7 100755 (executable)
@@ -28,15 +28,19 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.GraphLine;
 import jalview.datamodel.HiddenSequences;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.PDBEntry.Type;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
-import jalview.schemes.ResidueProperties;
 import jalview.schemes.UserColourScheme;
+import jalview.structure.StructureSelectionManager;
 
 import java.io.BufferedReader;
+import java.io.File;
 import java.io.FileReader;
+import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.StringReader;
 import java.net.URL;
@@ -88,8 +92,8 @@ public class AnnotationFile
   }
 
   /**
-   * convenience method for pre-2.8.3 annotation files which have no view,
-   * hidden columns or hidden row keywords.
+   * convenience method for pre-2.9 annotation files which have no view, hidden
+   * columns or hidden row keywords.
    * 
    * @param annotations
    * @param list
@@ -294,8 +298,7 @@ public class AnnotationFile
             }
             else
             {
-              graphGroup_refs.put(key, new Object[]
-              { refSeq, refGroup });
+              graphGroup_refs.put(key, new Object[] { refSeq, refGroup });
               graphGroup.put(key, row.label);
             }
           }
@@ -472,8 +475,9 @@ public class AnnotationFile
         text.append(properties.get(key));
       }
       // TODO: output alignment visualization settings here if required
-      // iterate through one or more views, defining, marking columns and rows as visible/hidden, and emmitting view properties.
-      // View specific annotation is 
+      // iterate through one or more views, defining, marking columns and rows
+      // as visible/hidden, and emmitting view properties.
+      // View specific annotation is
     }
 
     return text.toString();
@@ -670,14 +674,14 @@ public class AnnotationFile
     }
     boolean rslt = readAnnotationFile(viewport.getAlignment(), colSel,
             file, protocol);
-    if (rslt
-            && (colSel.hasSelectedColumns() || colSel.hasHiddenColumns()))
+    if (rslt && (colSel.hasSelectedColumns() || colSel.hasHiddenColumns()))
     {
       viewport.setColumnSelection(colSel);
     }
 
     return rslt;
   }
+
   public boolean readAnnotationFile(AlignmentI al, String file,
           String protocol)
   {
@@ -687,21 +691,41 @@ public class AnnotationFile
   public boolean readAnnotationFile(AlignmentI al, ColumnSelection colSel,
           String file, String protocol)
   {
+    baseUri = "";
     BufferedReader in = null;
     try
     {
       if (protocol.equals(AppletFormatAdapter.FILE))
       {
         in = new BufferedReader(new FileReader(file));
+        baseUri = new File(file).getParent();
+        if (baseUri == null)
+        {
+          baseUri = "";
+        }
+        else
+        {
+          baseUri += "/";
+        }
       }
       else if (protocol.equals(AppletFormatAdapter.URL))
       {
         URL url = new URL(file);
         in = new BufferedReader(new InputStreamReader(url.openStream()));
+        String bs = url.toExternalForm();
+        baseUri = bs.substring(0, bs.indexOf(url.getHost())
+                + url.getHost().length());
+        baseUri += url.toURI().getPath();
+        if (baseUri.lastIndexOf("/") > -1)
+        {
+          baseUri = baseUri.substring(0, baseUri.lastIndexOf("/")) + "/";
+        }
       }
       else if (protocol.equals(AppletFormatAdapter.PASTE))
       {
         in = new BufferedReader(new StringReader(file));
+        // TODO - support mimencoded PDBs for a paste.. ?
+        baseUri = "";
       }
       else if (protocol.equals(AppletFormatAdapter.CLASSLOADER))
       {
@@ -709,6 +733,8 @@ public class AnnotationFile
         if (is != null)
         {
           in = new BufferedReader(new java.io.InputStreamReader(is));
+          // TODO: this probably doesn't work for classloader - needs a test
+          baseUri = new File("/" + file).getParent() + "/";
         }
       }
       if (in != null)
@@ -734,11 +760,17 @@ public class AnnotationFile
 
   String lastread = "";
 
-  private static String GRAPHLINE = "GRAPHLINE", COMBINE = "COMBINE";
+  /**
+   * used for resolving absolute references to resources relative to
+   * annotationFile location
+   */
+  String baseUri = "";
+
+  private static String GRAPHLINE = "GRAPHLINE", COMBINE = "COMBINE",
+          STRUCTMODEL = "STRUCTMODEL";
 
   public boolean parseAnnotationFrom(AlignmentI al, ColumnSelection colSel,
-          BufferedReader in)
-          throws Exception
+          BufferedReader in) throws Exception
   {
     nlinesread = 0;
     ArrayList<Object[]> combineAnnotation_calls = new ArrayList<Object[]>();
@@ -831,8 +863,8 @@ public class AnnotationFile
         else if (token.equalsIgnoreCase(COMBINE))
         {
           // keep a record of current state and resolve groupRef at end
-          combineAnnotation_calls.add(new Object[]
-          { st, refSeq, groupRef });
+          combineAnnotation_calls
+                  .add(new Object[] { st, refSeq, groupRef });
           modified = true;
           continue;
         }
@@ -845,8 +877,8 @@ public class AnnotationFile
         else if (token.equalsIgnoreCase(GRAPHLINE))
         {
           // resolve at end
-          deferredAnnotation_calls.add(new Object[]
-          { GRAPHLINE, st, refSeq, groupRef });
+          deferredAnnotation_calls.add(new Object[] { GRAPHLINE, st,
+              refSeq, groupRef });
           modified = true;
           continue;
         }
@@ -981,7 +1013,43 @@ public class AnnotationFile
           modified = true;
           continue;
         }
-
+        else if (token.equalsIgnoreCase(STRUCTMODEL))
+        {
+          boolean failedtoadd = true;
+          // expect
+          // STRUCTMODEL <QUERYID> <TemplateID> <URL to model> <URL to
+          // alignment>
+          if (st.hasMoreTokens()) {
+            refSeq = al.findName(refSeqId = st.nextToken());
+            if (refSeq == null)
+            {
+              System.err.println("Couldn't locate " + refSeqId
+                      + " in the alignment for STRUCTMODEL");
+              refSeqId = null;
+            }
+            else
+            {
+              String tempId = st.nextToken();
+              String urlToModel = st.nextToken();
+              String urlToPairwise = st.hasMoreTokens() ? st.nextToken()
+                      : "";
+              if (add_structmodel(al, refSeq, tempId, urlToModel,
+                      urlToPairwise))
+              {
+                failedtoadd = false;
+              }
+            }
+          }
+          if (failedtoadd)
+          {
+            System.err
+                    .println("Need <QueryId> <TemplateId> <URL to Model> [<URL to pairwise alignment>] as tab separated fields after "
+                            + STRUCTMODEL);
+          } else {
+            modified = true;
+          }
+          continue;
+        }
         // Parse out the annotation row
         graphStyle = AlignmentAnnotation.getGraphValueFromString(token);
         label = st.nextToken();
@@ -1154,7 +1222,7 @@ public class AnnotationFile
                   (SequenceI) _deferred_args[2], // refSeq
                   (_deferred_args[3] == null) ? null : groupRefLookup
                           .get(_deferred_args[3]) // the reference
-                                                           // group, or null
+                                                  // group, or null
           );
         }
       }
@@ -1174,16 +1242,117 @@ public class AnnotationFile
                 (SequenceI) _combine_args[1], // refSeq
                 (_combine_args[2] == null) ? null : groupRefLookup
                         .get(_combine_args[2]) // the reference group,
-                                                        // or null
+                                               // or null
         );
       }
     }
     return modified;
   }
 
+  /**
+   * resolve a structural model and generate and add an alignment sequence for
+   * it
+   * 
+   * @param refSeq2
+   * @param tempId
+   * @param urlToModel
+   * @param urlToPairwise
+   * @return true if model and sequence was added
+   */
+  private boolean add_structmodel(AlignmentI al, SequenceI refSeq2, String tempId,
+          String urlToModel, String urlToPairwise)
+  {
+    String warningMessage = null, modelPath = null, modelProt = null, aliPath = null, aliProt = null;
+    boolean added = false;
+    try {
+      // locate tempId. if it exists, will need to merge, otherwise:
+      SequenceI templateSeq = al.findName(tempId);
+      // 1. load urlToModel
+      modelPath = resolveAbsolute(urlToModel);
+      modelProt = AppletFormatAdapter.checkProtocol(modelPath);
+      // need to transfer to local temp file ?
+      PDBEntry modelpe = new PDBEntry(tempId, null, Type.FILE, modelPath);
+      PDBEntry templpe = new PDBEntry(tempId, null, Type.FILE, modelPath);
+      refSeq2.addPDBId(modelpe);
+      aliPath = resolveAbsolute(urlToPairwise);
+      aliProt = AppletFormatAdapter.checkProtocol(aliPath);
+      // 2. load urlToPairwise
+      AlignmentI pwa = new AppletFormatAdapter().readFile(aliPath, aliProt,
+              "FASTA");
+      SequenceI qPw = null, tPw = null;
+      if (pwa != null)
+      {
+        // resolve query/template sequences in provided alignment
+        qPw = pwa.findName(refSeqId);
+        tPw = pwa.findName(tempId);
+      }
+      if (false)
+      // (qPw != null && tPw != null)
+      {
+        // not yet complete
+        // refalQ vvva--addrvvvtttddd
+        // refalT ---aaaa---sss---ddd
+        // profalQ ---v-v-v-a.-.-a---dd--r--vvvtt--td--dd
+        // profalT ---.-.-.-aa-a-a---..--.--sss..--.d--dd
+        // Pragmatic solution here:
+        // Map templpe onto refalT only where refalT and refalQ are both
+        // non-gaps
+
+        // columns for start..end in refSeq2
+        int[] gapMap = refSeq2.gapMap();
+        // insert gaps in tPw
+        int curi = 0, width = refSeq2.getLength();
+        // TBC
+      }
+      else
+      {
+        // assume 1:1 - so synthesise sequences to use to construct mapping
+        StructureFile pdbf = StructureSelectionManager
+                .getStructureSelectionManager().setMapping(false,
+                        new SequenceI[] { refSeq2.getDatasetSequence() },
+                        null, modelPath, modelProt);
+        refSeq2.getDatasetSequence().addPDBId(modelpe);
+        if (templateSeq == null && tPw != null)
+        {
+          tPw.createDatasetSequence();
+          tPw.getDatasetSequence().addPDBId(templpe); // needs to set mapping based on model yet...
+          al.addSequence(tPw);
+          added = true;
+        }
+      }
+    // 3. pad/insert gaps in urlToPairwise according to gaps already present in
+    // refSeq2
+    // 4. add padded tempId sequence to alignment
+    // 4. associate urlToModel with refSeq2 based on position map provided by
+    // urlToPairwise
+    // 5. associate urlToModel with tempId based on position map provided by
+    // urlToPairwise
+    // start a thread to load urlToModel and process/annotate sequences.
+    } catch (IOException x)
+    {
+      warningMessage = x.toString();
+    } finally {
+      if (warningMessage !=null)
+      {
+        System.err.println("Warnings whilst processing STRUCTMODEL: "+warningMessage);
+      }
+      return added;
+    }
+  }
+
+  private String resolveAbsolute(String relURI)
+  {
+    if (relURI.indexOf(":/") > -1 || relURI.startsWith("/")
+            || "".equals(baseUri) || relURI.startsWith(baseUri))
+    {
+      return relURI;
+    }
+    return baseUri + relURI;
+  }
+
   private void parseHideCols(ColumnSelection colSel, String nextToken)
   {
-    StringTokenizer inval = new StringTokenizer(nextToken,",");
+    StringTokenizer inval = new StringTokenizer(nextToken, ",");
     while (inval.hasMoreTokens())
     {
       String range = inval.nextToken().trim();
@@ -1301,8 +1470,8 @@ public class AnnotationFile
         }
       }
       if (hasSymbols
-              && (token.equals("H") || token.equals("E")
-                      || token.equals("S") || token.equals(" ")))
+              && (token.length() == 1 && "()<>[]{}AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
+                      .contains(token)))
       {
         // Either this character represents a helix or sheet
         // or an integer which can be displayed
@@ -1639,9 +1808,9 @@ public class AnnotationFile
         else if (key.equalsIgnoreCase("consThreshold"))
         {
           sg.cs.setConservationInc(Integer.parseInt(value));
-          Conservation c = new Conservation("Group",
-                  ResidueProperties.propHash, 3, sg.getSequences(null),
-                  sg.getStartRes(), sg.getEndRes() + 1);
+          Conservation c = new Conservation("Group", 3,
+                  sg.getSequences(null), sg.getStartRes(),
+                  sg.getEndRes() + 1);
 
           c.calculate();
           c.verdict(false, 25); // TODO: refer to conservation percent threshold
@@ -1761,6 +1930,10 @@ public class AnnotationFile
    */
   public String printCSVAnnotations(AlignmentAnnotation[] annotations)
   {
+    if (annotations == null)
+    {
+      return "";
+    }
     StringBuffer sp = new StringBuffer();
     for (int i = 0; i < annotations.length; i++)
     {