ensure successive matches to a regex have distinct annotation name (indice suffix...
[jalview.git] / src / jalview / io / AnnotationFile.java
index 2ce076a..288ce2f 100755 (executable)
@@ -33,15 +33,48 @@ public class AnnotationFile
       "JALVIEW_ANNOTATION\n"\r
       + "# Created: "\r
       + new java.util.Date() + "\n\n");\r
+  /**\r
+   * convenience method for pre-2.4 feature files which have no view, hidden columns or hidden row keywords.\r
+   * @param annotations\r
+   * @param groups\r
+   * @param properties\r
+   * @return feature file as a string.\r
+   */\r
+  public String printAnnotations(AlignmentAnnotation[] annotations,\r
+                                 Vector groups,\r
+                                 Hashtable properties)\r
+  {\r
+    return printAnnotations(annotations, groups,\r
+            properties, null);\r
 \r
+  }\r
+  /**\r
+   * hold all the information about a particular view definition\r
+   * read from or written out in an annotations file.\r
+   */\r
+  public class ViewDef {\r
+    public String viewname;\r
+    public HiddenSequences hidseqs;\r
+    public ColumnSelection hiddencols;\r
+    public Vector visibleGroups;\r
+    public ViewDef(String viewname, HiddenSequences hidseqs,\r
+            ColumnSelection hiddencols)\r
+    {\r
+      this.viewname = viewname;\r
+      this.hidseqs = hidseqs;\r
+      this.hiddencols = hiddencols;\r
+    }\r
+  }\r
   public String printAnnotations(AlignmentAnnotation[] annotations,\r
-                                 Vector groups)\r
+          Vector groups,\r
+          Hashtable properties, ViewDef[] views)\r
   {\r
     if (annotations != null)\r
     {\r
+      boolean oneColour = true;\r
       AlignmentAnnotation row;\r
       String comma;\r
-      SequenceI seqref = null;\r
+      SequenceI refSeq = null;\r
 \r
       StringBuffer colours = new StringBuffer();\r
       StringBuffer graphLine = new StringBuffer();\r
@@ -54,27 +87,28 @@ public class AnnotationFile
       {\r
         row = annotations[i];\r
 \r
-        if (!row.visible)\r
+        if (!row.visible && !row.hasScore())\r
         {\r
           continue;\r
         }\r
 \r
         color = null;\r
+        oneColour = true;\r
 \r
         if (row.sequenceRef == null)\r
         {\r
-          if (seqref != null)\r
+          if (refSeq != null)\r
           {\r
             text.append("\nSEQUENCE_REF\tALIGNMENT\n");\r
           }\r
 \r
-          seqref = null;\r
+          refSeq = null;\r
         }\r
 \r
-        else if (seqref == null || seqref != row.sequenceRef)\r
+        else if (refSeq == null || refSeq != row.sequenceRef)\r
         {\r
-          seqref = row.sequenceRef;\r
-          text.append("\nSEQUENCE_REF\t" + seqref.getName() + "\n");\r
+          refSeq = row.sequenceRef;\r
+          text.append("\nSEQUENCE_REF\t" + refSeq.getName() + "\n");\r
         }\r
 \r
         if (row.graph == AlignmentAnnotation.NO_GRAPH)\r
@@ -124,10 +158,10 @@ public class AnnotationFile
           text.append(row.description + "\t");\r
         }\r
 \r
-        for (int j = 0; j < row.annotations.length; j++)\r
+        for (int j = 0; row.annotations!=null && j < row.annotations.length; j++)\r
         {\r
-          if (seqref != null &&\r
-              jalview.util.Comparison.isGap(seqref.getCharAt(j)))\r
+          if (refSeq != null &&\r
+              jalview.util.Comparison.isGap(refSeq.getCharAt(j)))\r
           {\r
             continue;\r
           }\r
@@ -140,7 +174,8 @@ public class AnnotationFile
               text.append(comma + row.annotations[j].secondaryStructure);\r
               comma = ",";\r
             }\r
-            if (row.annotations[j].displayCharacter.length() > 0\r
+            if (row.annotations[j].displayCharacter!=null\r
+                && row.annotations[j].displayCharacter.length() > 0\r
                 && !row.annotations[j].displayCharacter.equals(" "))\r
             {\r
               text.append(comma + row.annotations[j].displayCharacter);\r
@@ -149,19 +184,35 @@ public class AnnotationFile
 \r
             if (row.annotations[j] != null)\r
             {\r
+              if(color!=null && !color.equals(row.annotations[j].colour))\r
+              {\r
+                oneColour = false;\r
+              }\r
+\r
               color = row.annotations[j].colour;\r
-              if (row.annotations[j].value != 0f)\r
+              if (row.annotations[j].value != 0f && row.annotations[j].value!=Float.NaN) \r
               {\r
                 text.append(comma + row.annotations[j].value);\r
               }\r
             }\r
+\r
+            if(row.annotations[j].colour!=null\r
+               && row.annotations[j].colour!=java.awt.Color.black)\r
+            {\r
+              text.append(comma+"["+\r
+                          jalview.util.Format.getHexString(\r
+                          row.annotations[j].colour)+"]");\r
+            }\r
           }\r
           text.append("|");\r
         }\r
 \r
+        if(row.hasScore())\r
+          text.append("\t"+row.score);\r
+\r
         text.append("\n");\r
 \r
-        if (color != null && color != java.awt.Color.black)\r
+        if (color != null && color != java.awt.Color.black && oneColour)\r
         {\r
           colours.append("COLOUR\t"\r
                          + row.label + "\t"\r
@@ -190,6 +241,18 @@ public class AnnotationFile
       printGroups(groups);\r
     }\r
 \r
+    if(properties!=null)\r
+    {\r
+      text.append("\n\nALIGNMENT");\r
+      Enumeration en = properties.keys();\r
+      while(en.hasMoreElements())\r
+      {\r
+        String key = en.nextElement().toString();\r
+        text.append("\t"+key+"="+properties.get(key));\r
+      }\r
+\r
+    }\r
+    \r
     return text.toString();\r
   }\r
 \r
@@ -249,6 +312,10 @@ public class AnnotationFile
       {\r
         text.append("textColThreshold=" + sg.thresholdTextColour);\r
       }\r
+      if (sg.idColour!=null)\r
+      {\r
+        text.append("idColour="+jalview.util.Format.getHexString(sg.idColour)+"\t");\r
+      }\r
 \r
       text.append("\n\n");\r
 \r
@@ -256,6 +323,7 @@ public class AnnotationFile
   }\r
 \r
   SequenceI refSeq = null;\r
+  String refSeqId = null;\r
   public boolean readAnnotationFile(AlignmentI al,\r
                                     String file,\r
                                     String protocol)\r
@@ -353,7 +421,13 @@ public class AnnotationFile
 \r
         else if (token.equalsIgnoreCase("SEQUENCE_REF"))\r
         {\r
-          refSeq = al.findName(st.nextToken());\r
+          if (st.hasMoreTokens())\r
+          {\r
+            refSeq = al.findName(refSeqId=st.nextToken());\r
+          if (refSeq==null)\r
+          {\r
+            refSeqId=null;\r
+          }\r
           try\r
           {\r
             refSeqIndex = Integer.parseInt(st.nextToken());\r
@@ -368,7 +442,10 @@ public class AnnotationFile
           {\r
             refSeqIndex = 1;\r
           }\r
-\r
+          } else {\r
+            refSeq = null;\r
+            refSeqId = null;\r
+          }\r
           continue ;\r
         }\r
 \r
@@ -384,65 +461,114 @@ public class AnnotationFile
           continue;\r
         }\r
 \r
-        graphStyle = AlignmentAnnotation.getGraphValueFromString(token);\r
-        label = st.nextToken();\r
-\r
-        if (st.countTokens() > 1)\r
+        else if( token.equalsIgnoreCase("BELOW_ALIGNMENT"))\r
         {\r
-          description = st.nextToken();\r
+          setBelowAlignment(al, st);\r
+          continue;\r
         }\r
-        else\r
+        else if( token.equalsIgnoreCase("ALIGNMENT"))\r
         {\r
-          description = null;\r
+          addAlignmentDetails(al, st);\r
+          continue;\r
         }\r
 \r
-        line = st.nextToken();\r
+        graphStyle = AlignmentAnnotation.getGraphValueFromString(token);\r
+        label = st.nextToken();\r
 \r
-        st = new StringTokenizer(line, "|", true);\r
-        annotations = new Annotation[alWidth];\r
 \r
         index = 0;\r
-        boolean emptyColumn = true;\r
+        annotations = new Annotation[alWidth];\r
+        description = null;\r
+        float score = Float.NaN;\r
 \r
-        while (st.hasMoreElements() && index < alWidth)\r
+        if(st.hasMoreTokens())\r
         {\r
-          token = st.nextToken().trim();\r
-          if (token.equals("|"))\r
+          line = st.nextToken();\r
+\r
+          if (line.indexOf("|") ==-1)\r
           {\r
-            if (emptyColumn)\r
-            {\r
-              index++;\r
-            }\r
+            description = line;\r
+            if (st.hasMoreTokens())\r
+              line = st.nextToken();\r
+          }\r
 \r
-            emptyColumn = true;\r
+          if(st.hasMoreTokens())\r
+          {\r
+            //This must be the score\r
+            score = Float.valueOf(st.nextToken()).floatValue();\r
           }\r
-          else\r
+\r
+          st = new StringTokenizer(line, "|", true);\r
+\r
+\r
+          boolean emptyColumn = true;\r
+          boolean onlyOneElement = (st.countTokens()==1);\r
+\r
+          while (st.hasMoreElements() && index < alWidth)\r
           {\r
-            annotations[index++] = parseAnnotation(token);\r
-            emptyColumn = false;\r
+            token = st.nextToken().trim();\r
+\r
+            if(onlyOneElement)\r
+            {\r
+              try\r
+              {\r
+                score = Float.valueOf(token).floatValue();\r
+                break;\r
+              }\r
+              catch(NumberFormatException ex){}\r
+            }\r
+\r
+            if (token.equals("|"))\r
+            {\r
+              if (emptyColumn)\r
+              {\r
+                index++;\r
+              }\r
+\r
+              emptyColumn = true;\r
+            }\r
+            else\r
+            {\r
+              annotations[index++] = parseAnnotation(token);\r
+              emptyColumn = false;\r
+            }\r
           }\r
+\r
         }\r
 \r
         annotation = new AlignmentAnnotation(label,\r
                                              description,\r
-                                             annotations,\r
+                                             (index==0) ? null : annotations,\r
                                              0,\r
                                              0,\r
                                              graphStyle);\r
 \r
+        annotation.score = score;\r
+\r
         if (refSeq != null)\r
         {\r
-          System.out.println(refSeq.getName()+" "+refSeqIndex);\r
-          annotation.createSequenceMapping(refSeq, refSeqIndex, false);\r
-          annotation.adjustForAlignment();\r
-          refSeq.addAlignmentAnnotation(annotation);\r
-        }\r
-\r
-        al.addAnnotation(annotation);\r
-\r
-        al.setAnnotationIndex(annotation,\r
+          annotation.belowAlignment=false;\r
+          do {\r
+            // copy before we do any mapping business.\r
+            // TODO: verify that undo/redo with 1:many sequence associated annotations can be undone correctly\r
+            AlignmentAnnotation ann = new AlignmentAnnotation(annotation);\r
+            annotation.createSequenceMapping(refSeq, refSeqIndex, false);\r
+            annotation.adjustForAlignment();\r
+            refSeq.addAlignmentAnnotation(annotation);\r
+            al.addAnnotation(annotation);\r
+            al.setAnnotationIndex(annotation,\r
+                                al.getAlignmentAnnotation().length - existingAnnotations -\r
+                                1);\r
+            // and recover our virgin copy to use again if necessary.\r
+            annotation = ann;\r
+            \r
+          } while (refSeqId!=null && (refSeq=al.findName(refSeq, refSeqId, true))!=null);\r
+        } else {\r
+          al.addAnnotation(annotation);\r
+          al.setAnnotationIndex(annotation,\r
                               al.getAlignmentAnnotation().length - existingAnnotations -\r
                               1);\r
+        }\r
       }\r
 \r
     }\r
@@ -457,10 +583,24 @@ public class AnnotationFile
 \r
   Annotation parseAnnotation(String string)\r
   {\r
-    String desc = null, displayChar = "";\r
+    String desc = null, displayChar = null;\r
     char ss = ' '; // secondaryStructure\r
     float value = 0;\r
     boolean parsedValue = false;\r
+\r
+    //find colour here\r
+    java.awt.Color colour = null;\r
+    int i=string.indexOf("[");\r
+    int j=string.indexOf("]");\r
+    if(i>-1 && j>-1)\r
+    {\r
+      UserColourScheme ucs = new UserColourScheme();\r
+\r
+      colour = ucs.getColourFromString(string.substring(i+1,j));\r
+\r
+      string = string.substring(0,i)+string.substring(j+1);\r
+    }\r
+\r
     StringTokenizer st = new StringTokenizer(string, ",");\r
     String token;\r
     while (st.hasMoreTokens())\r
@@ -501,19 +641,28 @@ public class AnnotationFile
 \r
     }\r
 \r
-    if (desc == null)\r
-    {\r
-      desc = value + "";\r
-    }\r
-\r
-    if (displayChar.length() > 1 && desc.length() == 1)\r
+    if (displayChar!=null\r
+        && displayChar.length() > 1\r
+        &&  desc!=null\r
+        && desc.length() == 1)\r
     {\r
       String tmp = displayChar;\r
       displayChar = desc;\r
       desc = tmp;\r
     }\r
+    /*\r
+     * In principle, this code will ensure that the Annotation element generated is renderable by any of the applet or application rendering code\r
+     * but instead we check for null strings when the display character is rendered. \r
+    if (displayChar==null)\r
+    {\r
+      displayChar="";\r
+    }\r
+    */\r
+    Annotation anot = new Annotation(displayChar, desc, ss, value);\r
 \r
-    return new Annotation(displayChar, desc, ss, value);\r
+    anot.colour = colour;\r
+\r
+    return anot;\r
   }\r
 \r
   void colourAnnotations(AlignmentI al, String label, String colour)\r
@@ -647,6 +796,8 @@ public class AnnotationFile
       }\r
     }\r
 \r
+\r
+\r
     if (refSeq != null)\r
     {\r
       sg.setStartRes(refSeq.findIndex(sg.getStartRes() + 1) - 1);\r
@@ -690,6 +841,8 @@ public class AnnotationFile
     if (sg != null)\r
     {\r
       String keyValue, key, value;\r
+      ColourSchemeI def = sg.cs;\r
+      sg.cs = null;\r
       while (st.hasMoreTokens())\r
       {\r
         keyValue = st.nextToken();\r
@@ -752,9 +905,76 @@ public class AnnotationFile
         {\r
           sg.thresholdTextColour = Integer.parseInt(value);\r
         }\r
-\r
+        else if (key.equalsIgnoreCase("idColour"))\r
+        {\r
+          // consider warning if colour doesn't resolve to a real colour\r
+          sg.setIdColour((def = new UserColourScheme(value)).findColour('A'));\r
+        }\r
         sg.recalcConservation();\r
       }\r
+      if (sg.cs==null)\r
+      {\r
+        sg.cs = def;\r
+      }\r
+    }\r
+  }\r
+\r
+  void setBelowAlignment(AlignmentI al, StringTokenizer st)\r
+  {\r
+    String token;\r
+    AlignmentAnnotation aa;\r
+    while(st.hasMoreTokens())\r
+    {\r
+      token = st.nextToken();\r
+      for(int i=0; i<al.getAlignmentAnnotation().length; i++)\r
+      {\r
+        aa = al.getAlignmentAnnotation()[i];\r
+        if(aa.sequenceRef==refSeq && aa.label.equals(token))\r
+        {\r
+          aa.belowAlignment = true;\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  void addAlignmentDetails(AlignmentI al, StringTokenizer st)\r
+  {\r
+    String keyValue, key, value;\r
+    while (st.hasMoreTokens())\r
+    {\r
+      keyValue = st.nextToken();\r
+      key = keyValue.substring(0, keyValue.indexOf("="));\r
+      value = keyValue.substring(keyValue.indexOf("=") + 1);\r
+      al.setProperty(key,value);\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Write annotations as a CSV file of the form 'label, value, value, ...' for each row.\r
+   * @param annotations\r
+   * @return CSV file as a string.\r
+   */\r
+  public String printCSVAnnotations(AlignmentAnnotation[] annotations)\r
+  {\r
+    StringBuffer sp = new StringBuffer();\r
+    for (int i=0; i<annotations.length; i++)\r
+    {\r
+      String atos = annotations[i].toString();\r
+      int p = 0;\r
+      do {\r
+        int cp = atos.indexOf("\n", p);\r
+        sp.append(annotations[i].label);\r
+        sp.append(",");\r
+        if (cp>p)\r
+        {\r
+          sp.append(atos.substring(p, cp+1));\r
+        } else {\r
+          sp.append(atos.substring(p));\r
+          sp.append("\n");\r
+        }\r
+        p = cp+1;\r
+      } while (p>0);\r
     }\r
+    return sp.toString();\r
   }\r
 }\r