Merge JAL-2136_phyre2_integration to develop
[jalview.git] / src / jalview / io / AnnotationFile.java
index 62503a0..b72184b 100755 (executable)
@@ -1,6 +1,6 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1)
- * Copyright (C) 2014 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
@@ -28,14 +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.gui.Desktop;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
-import jalview.schemes.ResidueProperties;
-import jalview.schemes.UserColourScheme;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.ColorUtils;
 
+import java.awt.Color;
 import java.io.BufferedReader;
+import java.io.File;
 import java.io.FileReader;
 import java.io.InputStreamReader;
 import java.io.StringReader;
@@ -241,7 +246,7 @@ public class AnnotationFile
                     && row.annotations[j].displayCharacter.length() > 0 && !row.annotations[j].displayCharacter
                     .equals(" "));
             hasGlyphs |= (row.annotations[j].secondaryStructure != 0 && row.annotations[j].secondaryStructure != ' ');
-            hasValues |= (row.annotations[j].value != Float.NaN); // NaNs can't
+            hasValues |= (!Float.isNaN(row.annotations[j].value)); // NaNs can't
             // be
             // rendered..
             hasText |= (row.annotations[j].description != null && row.annotations[j].description
@@ -294,8 +299,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);
             }
           }
@@ -331,13 +335,13 @@ public class AnnotationFile
             }
             if (hasValues)
             {
-              if (row.annotations[j].value != Float.NaN)
+              if (!Float.isNaN(row.annotations[j].value))
               {
                 text.append(comma + row.annotations[j].value);
               }
               else
               {
-                System.err.println("Skipping NaN - not valid value.");
+                // System.err.println("Skipping NaN - not valid value.");
                 text.append(comma + 0f);// row.annotations[j].value);
               }
               comma = ",";
@@ -472,8 +476,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();
@@ -582,7 +587,7 @@ public class AnnotationFile
       if (sg.cs != null)
       {
         text.append("colour=");
-        text.append(ColourSchemeProperty.getColourName(sg.cs));
+        text.append(sg.cs.toString());
         text.append("\t");
         if (sg.cs.getThreshold() != 0)
         {
@@ -661,7 +666,7 @@ public class AnnotationFile
   String refSeqId = null;
 
   public boolean annotateAlignmentView(AlignViewportI viewport,
-          String file, String protocol)
+          String file, DataSourceType protocol)
   {
     ColumnSelection colSel = viewport.getColumnSelection();
     if (colSel == null)
@@ -670,45 +675,67 @@ 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)
+          DataSourceType sourceType)
   {
-    return readAnnotationFile(al, null, file, protocol);
+    return readAnnotationFile(al, null, file, sourceType);
   }
 
   public boolean readAnnotationFile(AlignmentI al, ColumnSelection colSel,
-          String file, String protocol)
+          String file, DataSourceType sourceType)
   {
+    baseUri = "";
     BufferedReader in = null;
     try
     {
-      if (protocol.equals(AppletFormatAdapter.FILE))
+      if (sourceType == DataSourceType.FILE)
       {
         in = new BufferedReader(new FileReader(file));
+        baseUri = new File(file).getParent();
+        if (baseUri == null)
+        {
+          baseUri = "";
+        }
+        else
+        {
+          baseUri += "/";
+        }
       }
-      else if (protocol.equals(AppletFormatAdapter.URL))
+      else if (sourceType == DataSourceType.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))
+      else if (sourceType == DataSourceType.PASTE)
       {
         in = new BufferedReader(new StringReader(file));
+        // TODO - support mimencoded PDBs for a paste.. ?
+        baseUri = "";
       }
-      else if (protocol.equals(AppletFormatAdapter.CLASSLOADER))
+      else if (sourceType == DataSourceType.CLASSLOADER)
       {
         java.io.InputStream is = getClass().getResourceAsStream("/" + file);
         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 +761,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 +864,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 +878,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 +1014,54 @@ public class AnnotationFile
           modified = true;
           continue;
         }
-
+        else if (token.equalsIgnoreCase(STRUCTMODEL))
+        {
+          boolean failedtoadd = true;
+          // expect
+          // STRUCTMODEL <Query> <TemplateSeqId> <ModelFile> <FastaMappingFile>
+          // <Confidence> <%.I.D>
+          // <MatchStart> <MatchEnd> <Coverage> [<Other Information>]
+          String querySeqId = !st.hasMoreTokens() ? "" : st.nextToken();
+          SequenceI querySeq = al.findName(querySeqId);
+          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 fastaMapping = st.nextToken();
+              String confidence = !st.hasMoreTokens() ? "" : 100
+                      * Double.valueOf(st.nextToken()) + "";
+              String pid = !st.hasMoreTokens() ? "" : st.nextToken();
+              String alignRange = !st.hasMoreTokens() ? "" : st.nextToken()
+                      + "-" + st.nextToken();
+              String otherInfo = !st.hasMoreTokens() ? "" : st.nextToken();
+              String coverage = "";
+              if (add_structmodel(al, querySeq, refSeq, tempId,
+                      fastaMapping,
+                      alignRange, coverage,
+                      confidence, pid, otherInfo))
+              {
+                failedtoadd = false;
+              }
+            }
+          }
+          if (failedtoadd)
+          {
+            System.err
+                    .println("Need <Query> <TemplateSeqId> <ModelFile> <FastaMappingFile> <Confidence> <%.I.D> <MatchStart> <MatchEnd> <Coverage> [<Other Information>] as tab separated fields after"
+                            + STRUCTMODEL
+                            + ".\nNote: other information could be provided in html format ");
+          } else {
+            modified = true;
+          }
+          continue;
+        }
         // Parse out the annotation row
         graphStyle = AlignmentAnnotation.getGraphValueFromString(token);
         label = st.nextToken();
@@ -1154,7 +1234,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 +1254,113 @@ 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 querySequence,
+          SequenceI templateSeq,
+          String modelFile, String fastaFile, String aRange,
+          String coverage, String confidence,
+          String pid, String otherInfo)
+  {
+    String warningMessage = null;
+    boolean added = false;
+    try {
+      String structureModelFile = resolveAbsolute(modelFile);
+      String fastaMappingFile = resolveAbsolute(fastaFile.replaceAll(
+              ".fasta.jal", ".fasta"));
+      // System.out.println("Model File >> " + structureModelFile);
+      // System.out.println("Fasta File >> " + fastaMappingFile);
+      PDBEntry phyre2PDBEntry = new PDBEntry(modelFile, null, Type.FILE,
+              structureModelFile);
+      String phyre2ModelDesc = generatePhyre2InfoHTMLTable(aRange,
+              coverage, confidence, pid, otherInfo);
+      phyre2PDBEntry.setProperty("PHYRE2_MODEL_INFO", phyre2ModelDesc);
+      templateSeq.getDatasetSequence().addPDBId(phyre2PDBEntry);
+      if (querySequence != null)
+      {
+        querySequence.getDatasetSequence().addPDBId(phyre2PDBEntry);
+      }
+      StructureSelectionManager ssm = StructureSelectionManager
+              .getStructureSelectionManager(Desktop.instance);
+      ssm.registerPhyre2Template(structureModelFile, fastaMappingFile);
+      added = true;
+
+    } catch (Exception x)
+    {
+      warningMessage = x.toString();
+    } finally {
+      if (warningMessage !=null)
+      {
+        System.err.println("Warnings whilst processing STRUCTMODEL: "+warningMessage);
+      }
+    }
+    return added;
+  }
+
+  private String generatePhyre2InfoHTMLTable(String aRange,
+          String coverage, String confidence, String pid, String otherInfo)
+  {
+    StringBuilder phyre2InfoBuilder = new StringBuilder();
+    phyre2InfoBuilder.append("<html><table border=\"1\" width=100%>");
+    phyre2InfoBuilder
+            .append("<tr><td colspan=\"2\"><strong>Phyre2 Template Info</strong></td></tr>");
+    if (aRange != null && !aRange.isEmpty())
+    {
+      phyre2InfoBuilder.append("<tr><td>").append("Aligned range")
+              .append("</td><td>").append(aRange).append("</td></tr>");
+    }
+    if (coverage != null && !coverage.isEmpty())
+    {
+      phyre2InfoBuilder.append("<tr><td>").append("Coverage")
+              .append("</td><td>").append(coverage).append("</td></tr>");
+    }
+    if (confidence != null && !confidence.isEmpty())
+    {
+      phyre2InfoBuilder.append("<tr><td>").append("Confidence")
+              .append("</td><td>").append(confidence).append("</td></tr>");
+    }
+    if (pid != null && !pid.isEmpty())
+    {
+      phyre2InfoBuilder.append("<tr><td>").append("%.i.d")
+              .append("</td><td>").append(pid).append("</td></tr>");
+    }
+    if (otherInfo != null && !otherInfo.isEmpty())
+    {
+      phyre2InfoBuilder.append("<tr><td>").append("Other information")
+              .append("</td><td>").append(otherInfo).append("</td></tr>");
+    }
+    phyre2InfoBuilder.append("</table></html>");
+    return phyre2InfoBuilder.toString();
+  }
+
+  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();
@@ -1225,29 +1402,21 @@ public class AnnotationFile
 
   Annotation parseAnnotation(String string, int graphStyle)
   {
-    boolean hasSymbols = (graphStyle == AlignmentAnnotation.NO_GRAPH); // don't
-    // do the
-    // glyph
-    // test
-    // if we
-    // don't
-    // want
-    // secondary
-    // structure
+    // don't do the glyph test if we don't want secondary structure
+    boolean hasSymbols = (graphStyle == AlignmentAnnotation.NO_GRAPH);
     String desc = null, displayChar = null;
     char ss = ' '; // secondaryStructure
     float value = 0;
     boolean parsedValue = false, dcset = false;
 
     // find colour here
-    java.awt.Color colour = null;
+    Color colour = null;
     int i = string.indexOf("[");
     int j = string.indexOf("]");
     if (i > -1 && j > -1)
     {
-      UserColourScheme ucs = new UserColourScheme();
-
-      colour = ucs.getColourFromString(string.substring(i + 1, j));
+      colour = ColorUtils.parseColourString(string.substring(i + 1,
+              j));
       if (i > 0 && string.charAt(i - 1) == ',')
       {
         // clip the preceding comma as well
@@ -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
@@ -1349,7 +1518,7 @@ public class AnnotationFile
 
   void colourAnnotations(AlignmentI al, String label, String colour)
   {
-    UserColourScheme ucs = new UserColourScheme(colour);
+    Color awtColour = ColorUtils.parseColourString(colour);
     Annotation[] annotations;
     for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
     {
@@ -1360,7 +1529,7 @@ public class AnnotationFile
         {
           if (annotations[j] != null)
           {
-            annotations[j].colour = ucs.findColour('A');
+            annotations[j].colour = awtColour;
           }
         }
       }
@@ -1430,15 +1599,22 @@ public class AnnotationFile
           SequenceGroup groupRef)
   {
     String group = st.nextToken();
-    AlignmentAnnotation annotation = null, alannot[] = al
-            .getAlignmentAnnotation();
-    float value = new Float(st.nextToken()).floatValue();
+    AlignmentAnnotation[] alannot = al.getAlignmentAnnotation();
+    String nextToken = st.nextToken();
+    float value = 0f;
+    try
+    {
+      value = Float.valueOf(nextToken);
+    } catch (NumberFormatException e)
+    {
+      System.err.println("line " + nlinesread + ": Threshold '" + nextToken
+              + "' invalid, setting to zero");
+    }
     String label = st.hasMoreTokens() ? st.nextToken() : null;
-    java.awt.Color colour = null;
+    Color colour = null;
     if (st.hasMoreTokens())
     {
-      UserColourScheme ucs = new UserColourScheme(st.nextToken());
-      colour = ucs.findColour('A');
+      colour = ColorUtils.parseColourString(st.nextToken());
     }
     if (alannot != null)
     {
@@ -1452,10 +1628,6 @@ public class AnnotationFile
         }
       }
     }
-    if (annotation == null)
-    {
-      return;
-    }
   }
 
   void addGroup(AlignmentI al, StringTokenizer st)
@@ -1615,8 +1787,7 @@ public class AnnotationFile
     if (sg != null)
     {
       String keyValue, key, value;
-      ColourSchemeI def = sg.cs;
-      sg.cs = null;
+      ColourSchemeI def = sg.getColourScheme();
       while (st.hasMoreTokens())
       {
         keyValue = st.nextToken();
@@ -1629,7 +1800,8 @@ public class AnnotationFile
         }
         else if (key.equalsIgnoreCase("colour"))
         {
-          sg.cs = ColourSchemeProperty.getColour(al, value);
+          sg.cs.setColourScheme(ColourSchemeProperty
+                  .getColourScheme(al, value));
         }
         else if (key.equalsIgnoreCase("pidThreshold"))
         {
@@ -1639,8 +1811,7 @@ 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),
+          Conservation c = new Conservation("Group", sg.getSequences(null),
                   sg.getStartRes(), sg.getEndRes() + 1);
 
           c.calculate();
@@ -1651,7 +1822,7 @@ public class AnnotationFile
         }
         else if (key.equalsIgnoreCase("outlineColour"))
         {
-          sg.setOutlineColour(new UserColourScheme(value).findColour('A'));
+          sg.setOutlineColour(ColorUtils.parseColourString(value));
         }
         else if (key.equalsIgnoreCase("displayBoxes"))
         {
@@ -1671,11 +1842,11 @@ public class AnnotationFile
         }
         else if (key.equalsIgnoreCase("textCol1"))
         {
-          sg.textColour = new UserColourScheme(value).findColour('A');
+          sg.textColour = ColorUtils.parseColourString(value);
         }
         else if (key.equalsIgnoreCase("textCol2"))
         {
-          sg.textColour2 = new UserColourScheme(value).findColour('A');
+          sg.textColour2 = ColorUtils.parseColourString(value);
         }
         else if (key.equalsIgnoreCase("textColThreshold"))
         {
@@ -1683,9 +1854,8 @@ public class AnnotationFile
         }
         else if (key.equalsIgnoreCase("idColour"))
         {
-          // consider warning if colour doesn't resolve to a real colour
-          sg.setIdColour((def = new UserColourScheme(value))
-                  .findColour('A'));
+          Color idColour = ColorUtils.parseColourString(value);
+          sg.setIdColour(idColour == null ? Color.black : idColour);
         }
         else if (key.equalsIgnoreCase("hide"))
         {
@@ -1699,9 +1869,9 @@ public class AnnotationFile
         }
         sg.recalcConservation();
       }
-      if (sg.cs == null)
+      if (sg.getColourScheme() == null)
       {
-        sg.cs = def;
+        sg.setColourScheme(def);
       }
     }
   }
@@ -1761,6 +1931,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++)
     {