JAL-1299 fix: ensure a new graphGroup is created if first row identified in COMBINE...
[jalview.git] / src / jalview / io / AnnotationFile.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.io;
19
20 import java.io.*;
21 import java.net.*;
22 import java.util.*;
23
24 import jalview.analysis.*;
25 import jalview.datamodel.*;
26 import jalview.schemes.*;
27
28 public class AnnotationFile
29 {
30   public AnnotationFile()
31   {
32     init();
33   }
34
35   /**
36    * character used to write newlines
37    */
38   protected String newline = System.getProperty("line.separator");
39
40   /**
41    * set new line string and reset the output buffer
42    * 
43    * @param nl
44    */
45   public void setNewlineString(String nl)
46   {
47     newline = nl;
48     init();
49   }
50
51   public String getNewlineString()
52   {
53     return newline;
54   }
55
56   StringBuffer text;
57
58   private void init()
59   {
60     text = new StringBuffer("JALVIEW_ANNOTATION" + newline + "# Created: "
61             + new java.util.Date() + newline + newline);
62     refSeq = null;
63     refSeqId = null;
64   }
65
66   /**
67    * convenience method for pre-2.4 feature files which have no view, hidden
68    * columns or hidden row keywords.
69    * 
70    * @param annotations
71    * @param list
72    * @param properties
73    * @return feature file as a string.
74    */
75   public String printAnnotations(AlignmentAnnotation[] annotations,
76           List<SequenceGroup> list, Hashtable properties)
77   {
78     return printAnnotations(annotations, list, properties, null);
79
80   }
81
82   /**
83    * hold all the information about a particular view definition read from or
84    * written out in an annotations file.
85    */
86   public class ViewDef
87   {
88     public String viewname;
89
90     public HiddenSequences hidseqs;
91
92     public ColumnSelection hiddencols;
93
94     public Vector visibleGroups;
95
96     public Hashtable hiddenRepSeqs;
97
98     public ViewDef(String viewname, HiddenSequences hidseqs,
99             ColumnSelection hiddencols, Hashtable hiddenRepSeqs)
100     {
101       this.viewname = viewname;
102       this.hidseqs = hidseqs;
103       this.hiddencols = hiddencols;
104       this.hiddenRepSeqs = hiddenRepSeqs;
105     }
106   }
107
108   /**
109    * Prepare an annotation file given a set of annotations, groups, alignment
110    * properties and views.
111    * 
112    * @param annotations
113    * @param list
114    * @param properties
115    * @param views
116    * @return annotation file
117    */
118   public String printAnnotations(AlignmentAnnotation[] annotations,
119           List<SequenceGroup> list, Hashtable properties, ViewDef[] views)
120   {
121     // TODO: resolve views issue : annotationFile could contain visible region,
122     // or full data + hidden region specifications for a view.
123     if (annotations != null)
124     {
125       boolean oneColour = true;
126       AlignmentAnnotation row;
127       String comma;
128       SequenceI refSeq = null;
129       SequenceGroup refGroup = null;
130
131       StringBuffer colours = new StringBuffer();
132       StringBuffer graphLine = new StringBuffer();
133       StringBuffer rowprops = new StringBuffer();
134       Hashtable graphGroup = new Hashtable();
135       BitSet graphGroupSeen = new BitSet();
136
137       java.awt.Color color;
138
139       for (int i = 0; i < annotations.length; i++)
140       {
141         row = annotations[i];
142
143         if (!row.visible && !row.hasScore() && !(row.graphGroup>-1 && graphGroupSeen.get(row.graphGroup)))
144         {
145           continue;
146         }
147
148         color = null;
149         oneColour = true;
150
151         if (row.sequenceRef == null)
152         {
153           if (refSeq != null)
154           {
155             text.append(newline);
156             text.append("SEQUENCE_REF\tALIGNMENT");
157             text.append(newline);
158           }
159
160           refSeq = null;
161         }
162
163         else
164         {
165           if (refSeq == null || refSeq != row.sequenceRef)
166           {
167             refSeq = row.sequenceRef;
168             text.append(newline);
169             text.append("SEQUENCE_REF\t");
170             text.append(refSeq.getName());
171             text.append(newline);
172           }
173         }
174         // mark any group references for the row
175         if (row.groupRef == null)
176         {
177
178           if (refGroup != null)
179           {
180             text.append(newline);
181             text.append("GROUP_REF\tALIGNMENT");
182             text.append(newline);
183           }
184
185           refGroup = null;
186         }
187         else
188         {
189           if (refGroup == null || refGroup != row.groupRef)
190           {
191             refGroup = row.groupRef;
192             text.append(newline);
193             text.append("GROUP_REF\t");
194             text.append(refGroup.getName());
195             text.append(newline);
196           }
197         }
198
199         boolean hasGlyphs = row.hasIcons, hasLabels = row.hasText, hasValues = row.hasScore, hasText = false;
200         // lookahead to check what the annotation row object actually contains.
201         for (int j = 0; row.annotations != null
202                 && j < row.annotations.length
203                 && (!hasGlyphs || !hasLabels || !hasValues); j++)
204         {
205           if (row.annotations[j] != null)
206           {
207             hasLabels |= (row.annotations[j].displayCharacter != null
208                     && row.annotations[j].displayCharacter.length() > 0 && !row.annotations[j].displayCharacter
209                     .equals(" "));
210             hasGlyphs |= (row.annotations[j].secondaryStructure != 0 && row.annotations[j].secondaryStructure != ' ');
211             hasValues |= (row.annotations[j].value != Float.NaN); // NaNs can't
212             // be
213             // rendered..
214             hasText |= (row.annotations[j].description != null && row.annotations[j].description
215                     .length() > 0);
216           }
217         }
218
219         if (row.graph == AlignmentAnnotation.NO_GRAPH)
220         {
221           text.append("NO_GRAPH\t");
222           hasValues = false; // only secondary structure
223           // hasLabels = false; // and annotation description string.
224         }
225         else
226         {
227           if (row.graph == AlignmentAnnotation.BAR_GRAPH)
228           {
229             text.append("BAR_GRAPH\t");
230             hasGlyphs = false; // no secondary structure
231
232           }
233           else if (row.graph == AlignmentAnnotation.LINE_GRAPH)
234           {
235             hasGlyphs = false; // no secondary structure
236             text.append("LINE_GRAPH\t");
237           }
238
239           if (row.getThreshold() != null)
240           {
241             graphLine.append("GRAPHLINE\t");
242             graphLine.append(row.label);
243             graphLine.append("\t");
244             graphLine.append(row.getThreshold().value);
245             graphLine.append("\t");
246             graphLine.append(row.getThreshold().label);
247             graphLine.append("\t");
248             graphLine.append(jalview.util.Format.getHexString(row
249                     .getThreshold().colour));
250             graphLine.append(newline);
251           }
252
253           if (row.graphGroup > -1)
254           {
255             graphGroupSeen.set(row.graphGroup);
256             String key = String.valueOf(row.graphGroup);
257             if (graphGroup.containsKey(key))
258             {
259               graphGroup.put(key, graphGroup.get(key) + "\t" + row.label);
260             }
261             else
262             {
263               graphGroup.put(key, row.label);
264             }
265           }
266         }
267
268         text.append(row.label + "\t");
269         if (row.description != null)
270         {
271           text.append(row.description + "\t");
272         }
273         for (int j = 0; row.annotations != null
274                 && j < row.annotations.length; j++)
275         {
276           if (refSeq != null
277                   && jalview.util.Comparison.isGap(refSeq.getCharAt(j)))
278           {
279             continue;
280           }
281
282           if (row.annotations[j] != null)
283           {
284             comma = "";
285             if (hasGlyphs) // could be also hasGlyphs || ...
286             {
287
288               text.append(comma);
289               if (row.annotations[j].secondaryStructure != ' ')
290               {
291                 // only write out the field if its not whitespace.
292                 text.append(row.annotations[j].secondaryStructure);
293               }
294               comma = ",";
295             }
296             if (hasValues)
297             {
298               if (row.annotations[j].value != Float.NaN)
299               {
300                 text.append(comma + row.annotations[j].value);
301               }
302               else
303               {
304                 System.err.println("Skipping NaN - not valid value.");
305                 text.append(comma + 0f);// row.annotations[j].value);
306               }
307               comma = ",";
308             }
309             if (hasLabels)
310             {
311               // TODO: labels are emitted after values for bar graphs.
312               if // empty labels are allowed, so
313               (row.annotations[j].displayCharacter != null
314                       && row.annotations[j].displayCharacter.length() > 0
315                       && !row.annotations[j].displayCharacter.equals(" "))
316               {
317                 text.append(comma + row.annotations[j].displayCharacter);
318                 comma = ",";
319               }
320             }
321             if (hasText)
322             {
323               if (row.annotations[j].description != null
324                       && row.annotations[j].description.length() > 0
325                       && !row.annotations[j].description
326                               .equals(row.annotations[j].displayCharacter))
327               {
328                 text.append(comma + row.annotations[j].description);
329                 comma = ",";
330               }
331             }
332             if (color != null && !color.equals(row.annotations[j].colour))
333             {
334               oneColour = false;
335             }
336
337             color = row.annotations[j].colour;
338
339             if (row.annotations[j].colour != null
340                     && row.annotations[j].colour != java.awt.Color.black)
341             {
342               text.append(comma
343                       + "["
344                       + jalview.util.Format
345                               .getHexString(row.annotations[j].colour)
346                       + "]");
347               comma = ",";
348             }
349           }
350           text.append("|");
351         }
352
353         if (row.hasScore())
354           text.append("\t" + row.score);
355
356         text.append(newline);
357
358         if (color != null && color != java.awt.Color.black && oneColour)
359         {
360           colours.append("COLOUR\t");
361           colours.append(row.label);
362           colours.append("\t");
363           colours.append(jalview.util.Format.getHexString(color));
364           colours.append(newline);
365         }
366         if (row.scaleColLabel || row.showAllColLabels
367                 || row.centreColLabels)
368         {
369           rowprops.append("ROWPROPERTIES\t");
370           rowprops.append(row.label);
371           rowprops.append("\tscaletofit=");
372           rowprops.append(row.scaleColLabel);
373           rowprops.append("\tshowalllabs=");
374           rowprops.append(row.showAllColLabels);
375           rowprops.append("\tcentrelabs=");
376           rowprops.append(row.centreColLabels);
377           rowprops.append(newline);
378         }
379       }
380
381       text.append(newline);
382
383       text.append(colours.toString());
384       text.append(graphLine.toString());
385       if (graphGroup.size() > 0)
386       {
387         Enumeration en = graphGroup.elements();
388         while (en.hasMoreElements())
389         {
390           text.append("COMBINE\t");
391           text.append(en.nextElement());
392           text.append(newline);
393         }
394       }
395       text.append(rowprops.toString());
396     }
397
398     if (list != null)
399     {
400       printGroups(list);
401     }
402
403     if (properties != null)
404     {
405       text.append(newline);
406       text.append(newline);
407       text.append("ALIGNMENT");
408       Enumeration en = properties.keys();
409       while (en.hasMoreElements())
410       {
411         String key = en.nextElement().toString();
412         text.append("\t");
413         text.append(key);
414         text.append("=");
415         text.append(properties.get(key));
416       }
417       // TODO: output alignment visualization settings here if required
418
419     }
420
421     return text.toString();
422   }
423
424   public void printGroups(List<SequenceGroup> list)
425   {
426     SequenceI seqrep = null;
427     for (SequenceGroup sg : list)
428     {
429       if (!sg.hasSeqrep())
430       {
431         text.append("SEQUENCE_GROUP\t" + sg.getName() + "\t"
432                 + (sg.getStartRes() + 1) + "\t" + (sg.getEndRes() + 1)
433                 + "\t" + "-1\t");
434         seqrep = null;
435       }
436       else
437       {
438         seqrep = sg.getSeqrep();
439         text.append("SEQUENCE_REF\t");
440         text.append(seqrep.getName());
441         text.append(newline);
442         text.append("SEQUENCE_GROUP\t");
443         text.append(sg.getName());
444         text.append("\t");
445         text.append((seqrep.findPosition(sg.getStartRes())));
446         text.append("\t");
447         text.append((seqrep.findPosition(sg.getEndRes())));
448         text.append("\t");
449         text.append("-1\t");
450       }
451       for (int s = 0; s < sg.getSize(); s++)
452       {
453         text.append(sg.getSequenceAt(s).getName());
454         text.append("\t");
455       }
456       text.append(newline);
457       text.append("PROPERTIES\t");
458       text.append(sg.getName());
459       text.append("\t");
460
461       if (sg.getDescription() != null)
462       {
463         text.append("description=");
464         text.append(sg.getDescription());
465         text.append("\t");
466       }
467       if (sg.cs != null)
468       {
469         text.append("colour=");
470         text.append(ColourSchemeProperty.getColourName(sg.cs));
471         text.append("\t");
472         if (sg.cs.getThreshold() != 0)
473         {
474           text.append("pidThreshold=");
475           text.append(sg.cs.getThreshold());
476         }
477         if (sg.cs.conservationApplied())
478         {
479           text.append("consThreshold=");
480           text.append(sg.cs.getConservationInc());
481           text.append("\t");
482         }
483       }
484       text.append("outlineColour=");
485       text.append(jalview.util.Format.getHexString(sg.getOutlineColour()));
486       text.append("\t");
487
488       text.append("displayBoxes=");
489       text.append(sg.getDisplayBoxes());
490       text.append("\t");
491       text.append("displayText=");
492       text.append(sg.getDisplayText());
493       text.append("\t");
494       text.append("colourText=");
495       text.append(sg.getColourText());
496       text.append("\t");
497       text.append("showUnconserved=");
498       text.append(sg.getShowNonconserved());
499       text.append("\t");
500       if (sg.textColour != java.awt.Color.black)
501       {
502         text.append("textCol1=");
503         text.append(jalview.util.Format.getHexString(sg.textColour));
504         text.append("\t");
505       }
506       if (sg.textColour2 != java.awt.Color.white)
507       {
508         text.append("textCol2=");
509         text.append(jalview.util.Format.getHexString(sg.textColour2));
510         text.append("\t");
511       }
512       if (sg.thresholdTextColour != 0)
513       {
514         text.append("textColThreshold=");
515         text.append(sg.thresholdTextColour);
516         text.append("\t");
517       }
518       if (sg.idColour != null)
519       {
520         text.append("idColour=");
521         text.append(jalview.util.Format.getHexString(sg.idColour));
522         text.append("\t");
523       }
524       if (sg.isHidereps())
525       {
526         text.append("hide=true\t");
527       }
528       if (sg.isHideCols())
529       {
530         text.append("hidecols=true\t");
531       }
532       if (seqrep != null)
533       {
534         // terminate the last line and clear the sequence ref for the group
535         text.append(newline);
536         text.append("SEQUENCE_REF");
537       }
538       text.append(newline);
539       text.append(newline);
540
541     }
542   }
543
544   SequenceI refSeq = null;
545
546   String refSeqId = null;
547
548   public boolean readAnnotationFile(AlignmentI al, String file,
549           String protocol)
550   {
551     BufferedReader in = null;
552     try
553     {
554       if (protocol.equals(AppletFormatAdapter.FILE))
555       {
556         in = new BufferedReader(new FileReader(file));
557       }
558       else if (protocol.equals(AppletFormatAdapter.URL))
559       {
560         URL url = new URL(file);
561         in = new BufferedReader(new InputStreamReader(url.openStream()));
562       }
563       else if (protocol.equals(AppletFormatAdapter.PASTE))
564       {
565         in = new BufferedReader(new StringReader(file));
566       }
567       else if (protocol.equals(AppletFormatAdapter.CLASSLOADER))
568       {
569         java.io.InputStream is = getClass().getResourceAsStream("/" + file);
570         if (is != null)
571         {
572           in = new BufferedReader(new java.io.InputStreamReader(is));
573         }
574       }
575       if (in != null)
576       {
577         return parseAnnotationFrom(al, in);
578       }
579
580     } catch (Exception ex)
581     {
582       ex.printStackTrace();
583       System.out.println("Problem reading annotation file: " + ex);
584       if (nlinesread>0) {
585         System.out.println("Last read line "+nlinesread+": '"+lastread+"' (first 80 chars) ...");
586       }
587       return false;
588     }
589     return false;
590   }
591   long nlinesread=0;
592   String lastread="";
593   public boolean parseAnnotationFrom(AlignmentI al, BufferedReader in)
594           throws Exception
595   {
596     nlinesread = 0;
597     /**
598      * number of combine statements in this annotation file. Used to create new groups for combined annotation graphs without disturbing existing ones
599      */
600     int combinecount = 0;
601     boolean modified = false;
602     String groupRef = null;
603     Hashtable groupRefRows = new Hashtable();
604
605     Hashtable autoAnnots = new Hashtable();
606     {
607       String line, label, description, token;
608       int graphStyle, index;
609       int refSeqIndex = 1;
610       int existingAnnotations = 0;
611       // when true - will add new rows regardless of whether they are duplicate
612       // auto-annotation like consensus or conservation graphs
613       boolean overrideAutoAnnot = false;
614       if (al.getAlignmentAnnotation() != null)
615       {
616         existingAnnotations = al.getAlignmentAnnotation().length;
617         if (existingAnnotations > 0)
618         {
619           AlignmentAnnotation[] aa = al.getAlignmentAnnotation();
620           for (int aai = 0; aai < aa.length; aai++)
621           {
622             if (aa[aai].autoCalculated)
623             {
624               // make a note of the name and description
625               autoAnnots.put(
626                       autoAnnotsKey(aa[aai], aa[aai].sequenceRef,
627                               (aa[aai].groupRef == null ? null
628                                       : aa[aai].groupRef.getName())),
629                       new Integer(1));
630             }
631           }
632         }
633       }
634
635       int alWidth = al.getWidth();
636
637       StringTokenizer st;
638       Annotation[] annotations;
639       AlignmentAnnotation annotation = null;
640
641       // First confirm this is an Annotation file
642       boolean jvAnnotationFile = false;
643       while ((line = in.readLine()) != null)
644       {
645         nlinesread++;lastread = new String(line);
646         if (line.indexOf("#") == 0)
647         {
648           continue;
649         }
650
651         if (line.indexOf("JALVIEW_ANNOTATION") > -1)
652         {
653           jvAnnotationFile = true;
654           break;
655         }
656       }
657
658       if (!jvAnnotationFile)
659       {
660         in.close();
661         return false;
662       }
663
664       while ((line = in.readLine()) != null)
665       {
666         nlinesread++;lastread = new String(line);
667         if (line.indexOf("#") == 0
668                 || line.indexOf("JALVIEW_ANNOTATION") > -1
669                 || line.length() == 0)
670         {
671           continue;
672         }
673
674         st = new StringTokenizer(line, "\t");
675         token = st.nextToken();
676         if (token.equalsIgnoreCase("COLOUR"))
677         {
678           // TODO: use graduated colour def'n here too
679           colourAnnotations(al, st.nextToken(), st.nextToken());
680           modified = true;
681           continue;
682         }
683
684         else if (token.equalsIgnoreCase("COMBINE"))
685         {
686           combineAnnotations(al, combinecount++, st);
687           modified = true;
688           continue;
689         }
690         else if (token.equalsIgnoreCase("ROWPROPERTIES"))
691         {
692           addRowProperties(al, st);
693           modified = true;
694           continue;
695         }
696         else if (token.equalsIgnoreCase("GRAPHLINE"))
697         {
698           addLine(al, st);
699           modified = true;
700           continue;
701         }
702
703         else if (token.equalsIgnoreCase("SEQUENCE_REF"))
704         {
705           if (st.hasMoreTokens())
706           {
707             refSeq = al.findName(refSeqId = st.nextToken());
708             if (refSeq == null)
709             {
710               refSeqId = null;
711             }
712             try
713             {
714               refSeqIndex = Integer.parseInt(st.nextToken());
715               if (refSeqIndex < 1)
716               {
717                 refSeqIndex = 1;
718                 System.out
719                         .println("WARNING: SEQUENCE_REF index must be > 0 in AnnotationFile");
720               }
721             } catch (Exception ex)
722             {
723               refSeqIndex = 1;
724             }
725           }
726           else
727           {
728             refSeq = null;
729             refSeqId = null;
730           }
731           continue;
732         }
733         else if (token.equalsIgnoreCase("GROUP_REF"))
734         {
735           // Group references could be forward or backwards, so they are
736           // resolved after the whole file is read in
737           groupRef = null;
738           if (st.hasMoreTokens())
739           {
740             groupRef = st.nextToken();
741             if (groupRef.length() < 1)
742             {
743               groupRef = null; // empty string
744             }
745             else
746             {
747               if (groupRefRows.get(groupRef) == null)
748               {
749                 groupRefRows.put(groupRef, new Vector());
750               }
751             }
752           }
753           continue;
754         }
755         else if (token.equalsIgnoreCase("SEQUENCE_GROUP"))
756         {
757           addGroup(al, st);
758           continue;
759         }
760
761         else if (token.equalsIgnoreCase("PROPERTIES"))
762         {
763           addProperties(al, st);
764           modified = true;
765           continue;
766         }
767
768         else if (token.equalsIgnoreCase("BELOW_ALIGNMENT"))
769         {
770           setBelowAlignment(al, st);
771           modified = true;
772           continue;
773         }
774         else if (token.equalsIgnoreCase("ALIGNMENT"))
775         {
776           addAlignmentDetails(al, st);
777           modified = true;
778           continue;
779         }
780
781         // Parse out the annotation row
782         graphStyle = AlignmentAnnotation.getGraphValueFromString(token);
783         label = st.nextToken();
784
785         index = 0;
786         annotations = new Annotation[alWidth];
787         description = null;
788         float score = Float.NaN;
789
790         if (st.hasMoreTokens())
791         {
792           line = st.nextToken();
793
794           if (line.indexOf("|") == -1)
795           {
796             description = line;
797             if (st.hasMoreTokens())
798               line = st.nextToken();
799           }
800
801           if (st.hasMoreTokens())
802           {
803             // This must be the score
804             score = Float.valueOf(st.nextToken()).floatValue();
805           }
806
807           st = new StringTokenizer(line, "|", true);
808
809           boolean emptyColumn = true;
810           boolean onlyOneElement = (st.countTokens() == 1);
811
812           while (st.hasMoreElements() && index < alWidth)
813           {
814             token = st.nextToken().trim();
815
816             if (onlyOneElement)
817             {
818               try
819               {
820                 score = Float.valueOf(token).floatValue();
821                 break;
822               } catch (NumberFormatException ex)
823               {
824               }
825             }
826
827             if (token.equals("|"))
828             {
829               if (emptyColumn)
830               {
831                 index++;
832               }
833
834               emptyColumn = true;
835             }
836             else
837             {
838               annotations[index++] = parseAnnotation(token, graphStyle);
839               emptyColumn = false;
840             }
841           }
842
843         }
844
845         annotation = new AlignmentAnnotation(label, description,
846                 (index == 0) ? null : annotations, 0, 0, graphStyle);
847
848         annotation.score = score;
849         if (!overrideAutoAnnot
850                 && autoAnnots.containsKey(autoAnnotsKey(annotation, refSeq,
851                         groupRef)))
852         {
853           // skip - we've already got an automatic annotation of this type.
854           continue;
855         }
856         // otherwise add it!
857         if (refSeq != null)
858         {
859
860           annotation.belowAlignment = false;
861           // make a copy of refSeq so we can find other matches in the alignment
862           SequenceI referedSeq = refSeq;
863           do
864           {
865             // copy before we do any mapping business.
866             // TODO: verify that undo/redo with 1:many sequence associated
867             // annotations can be undone correctly
868             AlignmentAnnotation ann = new AlignmentAnnotation(annotation);
869             annotation
870                     .createSequenceMapping(referedSeq, refSeqIndex, false);
871             annotation.adjustForAlignment();
872             referedSeq.addAlignmentAnnotation(annotation);
873             al.addAnnotation(annotation);
874             al.setAnnotationIndex(annotation,
875                     al.getAlignmentAnnotation().length
876                             - existingAnnotations - 1);
877             if (groupRef != null)
878             {
879               ((Vector) groupRefRows.get(groupRef)).addElement(annotation);
880             }
881             // and recover our virgin copy to use again if necessary.
882             annotation = ann;
883
884           } while (refSeqId != null
885                   && (referedSeq = al.findName(referedSeq, refSeqId, true)) != null);
886         }
887         else
888         {
889           al.addAnnotation(annotation);
890           al.setAnnotationIndex(annotation,
891                   al.getAlignmentAnnotation().length - existingAnnotations
892                           - 1);
893           if (groupRef != null)
894           {
895             ((Vector) groupRefRows.get(groupRef)).addElement(annotation);
896           }
897         }
898         // and set modification flag
899         modified = true;
900       }
901       // Finally, resolve the groupRefs
902       Enumeration en = groupRefRows.keys();
903
904       while (en.hasMoreElements())
905       {
906         groupRef = (String) en.nextElement();
907         boolean matched = false;
908         // Resolve group: TODO: add a getGroupByName method to alignments
909         for (SequenceGroup theGroup : al.getGroups())
910         {
911           if (theGroup.getName().equals(groupRef))
912           {
913             if (matched)
914             {
915               // TODO: specify and implement duplication of alignment annotation
916               // for multiple group references.
917               System.err
918                       .println("Ignoring 1:many group reference mappings for group name '"
919                               + groupRef + "'");
920             }
921             else
922             {
923               matched = true;
924               Vector rowset = (Vector) groupRefRows.get(groupRef);
925               if (rowset != null && rowset.size() > 0)
926               {
927                 AlignmentAnnotation alan = null;
928                 for (int elm = 0, elmSize = rowset.size(); elm < elmSize; elm++)
929                 {
930                   alan = (AlignmentAnnotation) rowset.elementAt(elm);
931                   alan.groupRef = theGroup;
932                 }
933               }
934             }
935           }
936         }
937         ((Vector) groupRefRows.get(groupRef)).removeAllElements();
938       }
939     }
940     return modified;
941   }
942
943   private Object autoAnnotsKey(AlignmentAnnotation annotation,
944           SequenceI refSeq, String groupRef)
945   {
946     return annotation.graph + "\t" + annotation.label + "\t"
947             + annotation.description + "\t"
948             + (refSeq != null ? refSeq.getDisplayId(true) : "");
949   }
950
951   Annotation parseAnnotation(String string, int graphStyle)
952   {
953     boolean hasSymbols = (graphStyle == AlignmentAnnotation.NO_GRAPH); // don't
954     // do the
955     // glyph
956     // test
957     // if we
958     // don't
959     // want
960     // secondary
961     // structure
962     String desc = null, displayChar = null;
963     char ss = ' '; // secondaryStructure
964     float value = 0;
965     boolean parsedValue = false, dcset = false;
966
967     // find colour here
968     java.awt.Color colour = null;
969     int i = string.indexOf("[");
970     int j = string.indexOf("]");
971     if (i > -1 && j > -1)
972     {
973       UserColourScheme ucs = new UserColourScheme();
974
975       colour = ucs.getColourFromString(string.substring(i + 1, j));
976       if (i > 0 && string.charAt(i - 1) == ',')
977       {
978         // clip the preceding comma as well
979         i--;
980       }
981       string = string.substring(0, i) + string.substring(j + 1);
982     }
983
984     StringTokenizer st = new StringTokenizer(string, ",", true);
985     String token;
986     boolean seenContent = false;
987     int pass = 0;
988     while (st.hasMoreTokens())
989     {
990       pass++;
991       token = st.nextToken().trim();
992       if (token.equals(","))
993       {
994         if (!seenContent && parsedValue && !dcset)
995         {
996           // allow the value below the bar/line to be empty
997           dcset = true;
998           displayChar = " ";
999         }
1000         seenContent = false;
1001         continue;
1002       }
1003       else
1004       {
1005         seenContent = true;
1006       }
1007
1008       if (!parsedValue)
1009       {
1010         try
1011         {
1012           displayChar = token;
1013           // foo
1014           value = new Float(token).floatValue();
1015           parsedValue = true;
1016           continue;
1017         } catch (NumberFormatException ex)
1018         {
1019         }
1020       }
1021       else
1022       {
1023         if (token.length() == 1)
1024         {
1025           displayChar = token;
1026         }
1027       }
1028       if (hasSymbols
1029               && (token.equals("H") || token.equals("E")
1030                       || token.equals("S") || token.equals(" ")))
1031       {
1032         // Either this character represents a helix or sheet
1033         // or an integer which can be displayed
1034         ss = token.charAt(0);
1035         if (displayChar.equals(token.substring(0, 1)))
1036         {
1037           displayChar = "";
1038         }
1039       }
1040       else if (desc == null || (parsedValue && pass > 2))
1041       {
1042         desc = token;
1043       }
1044
1045     }
1046     // if (!dcset && string.charAt(string.length() - 1) == ',')
1047     // {
1048     // displayChar = " "; // empty display char symbol.
1049     // }
1050     if (displayChar != null && desc != null && desc.length() == 1)
1051     {
1052       if (displayChar.length() > 1)
1053       {
1054         // switch desc and displayChar - legacy support
1055         String tmp = displayChar;
1056         displayChar = desc;
1057         desc = tmp;
1058       }
1059       else
1060       {
1061         if (displayChar.equals(desc))
1062         {
1063           // duplicate label - hangover from the 'robust parser' above
1064           desc = null;
1065         }
1066       }
1067     }
1068     Annotation anot = new Annotation(displayChar, desc, ss, value);
1069
1070     anot.colour = colour;
1071
1072     return anot;
1073   }
1074
1075   void colourAnnotations(AlignmentI al, String label, String colour)
1076   {
1077     UserColourScheme ucs = new UserColourScheme(colour);
1078     Annotation[] annotations;
1079     for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1080     {
1081       if (al.getAlignmentAnnotation()[i].label.equalsIgnoreCase(label))
1082       {
1083         annotations = al.getAlignmentAnnotation()[i].annotations;
1084         for (int j = 0; j < annotations.length; j++)
1085         {
1086           if (annotations[j] != null)
1087           {
1088             annotations[j].colour = ucs.findColour('A');
1089           }
1090         }
1091       }
1092     }
1093   }
1094
1095   void combineAnnotations(AlignmentI al, int combineCount, StringTokenizer st)
1096   {
1097     String group = st.nextToken();
1098     // First make sure we are not overwriting the graphIndex
1099     int graphGroup=0;
1100     if (al.getAlignmentAnnotation() != null)
1101     {
1102       for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1103       {
1104         AlignmentAnnotation aa = al.getAlignmentAnnotation()[i];
1105         if (aa.graphGroup>graphGroup)
1106         {
1107           // try to number graphGroups in order of occurence.
1108           graphGroup=aa.graphGroup;
1109         }
1110         if (aa.label.equalsIgnoreCase(group))
1111         {
1112           if (aa.graphGroup>-1)
1113           {
1114             graphGroup = aa.graphGroup;
1115           } else {
1116             if (graphGroup >= combineCount)
1117             {
1118               graphGroup++;
1119             }
1120             aa.graphGroup = graphGroup;
1121           }
1122           break;
1123         }
1124       }
1125
1126       // Now update groups
1127       while (st.hasMoreTokens())
1128       {
1129         group = st.nextToken();
1130         for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1131         {
1132           if (al.getAlignmentAnnotation()[i].label.equalsIgnoreCase(group))
1133           {
1134             al.getAlignmentAnnotation()[i].graphGroup = graphGroup;
1135             break;
1136           }
1137         }
1138       }
1139     }
1140     else
1141     {
1142       System.err
1143               .println("Couldn't combine annotations. None are added to alignment yet!");
1144     }
1145   }
1146
1147   void addLine(AlignmentI al, StringTokenizer st)
1148   {
1149     String group = st.nextToken();
1150     AlignmentAnnotation annotation = null, alannot[] = al
1151             .getAlignmentAnnotation();
1152     if (alannot != null)
1153     {
1154       for (int i = 0; i < alannot.length; i++)
1155       {
1156         if (alannot[i].label.equalsIgnoreCase(group))
1157         {
1158           annotation = alannot[i];
1159           break;
1160         }
1161       }
1162     }
1163     if (annotation == null)
1164     {
1165       return;
1166     }
1167     float value = new Float(st.nextToken()).floatValue();
1168     String label = st.hasMoreTokens() ? st.nextToken() : null;
1169     java.awt.Color colour = null;
1170     if (st.hasMoreTokens())
1171     {
1172       UserColourScheme ucs = new UserColourScheme(st.nextToken());
1173       colour = ucs.findColour('A');
1174     }
1175
1176     annotation.setThreshold(new GraphLine(value, label, colour));
1177   }
1178
1179   void addGroup(AlignmentI al, StringTokenizer st)
1180   {
1181     SequenceGroup sg = new SequenceGroup();
1182     sg.setName(st.nextToken());
1183     String rng = "";
1184     try
1185     {
1186       rng = st.nextToken();
1187       if (rng.length() > 0 && !rng.startsWith("*"))
1188       {
1189         sg.setStartRes(Integer.parseInt(rng) - 1);
1190       }
1191       else
1192       {
1193         sg.setStartRes(0);
1194       }
1195       rng = st.nextToken();
1196       if (rng.length() > 0 && !rng.startsWith("*"))
1197       {
1198         sg.setEndRes(Integer.parseInt(rng) - 1);
1199       }
1200       else
1201       {
1202         sg.setEndRes(al.getWidth() - 1);
1203       }
1204     } catch (Exception e)
1205     {
1206       System.err
1207               .println("Couldn't parse Group Start or End Field as '*' or a valid column or sequence index: '"
1208                       + rng + "' - assuming alignment width for group.");
1209       // assume group is full width
1210       sg.setStartRes(0);
1211       sg.setEndRes(al.getWidth() - 1);
1212     }
1213
1214     String index = st.nextToken();
1215     if (index.equals("-1"))
1216     {
1217       while (st.hasMoreElements())
1218       {
1219         sg.addSequence(al.findName(st.nextToken()), false);
1220       }
1221     }
1222     else
1223     {
1224       StringTokenizer st2 = new StringTokenizer(index, ",");
1225
1226       while (st2.hasMoreTokens())
1227       {
1228         String tmp = st2.nextToken();
1229         if (tmp.equals("*"))
1230         {
1231           for (int i = 0; i < al.getHeight(); i++)
1232           {
1233             sg.addSequence(al.getSequenceAt(i), false);
1234           }
1235         }
1236         else if (tmp.indexOf("-") >= 0)
1237         {
1238           StringTokenizer st3 = new StringTokenizer(tmp, "-");
1239
1240           int start = (Integer.parseInt(st3.nextToken()));
1241           int end = (Integer.parseInt(st3.nextToken()));
1242
1243           if (end > start)
1244           {
1245             for (int i = start; i <= end; i++)
1246             {
1247               sg.addSequence(al.getSequenceAt(i - 1), false);
1248             }
1249           }
1250         }
1251         else
1252         {
1253           sg.addSequence(al.getSequenceAt(Integer.parseInt(tmp) - 1), false);
1254         }
1255       }
1256     }
1257
1258     if (refSeq != null)
1259     {
1260       sg.setStartRes(refSeq.findIndex(sg.getStartRes() + 1) - 1);
1261       sg.setEndRes(refSeq.findIndex(sg.getEndRes() + 1) - 1);
1262       sg.setSeqrep(refSeq);
1263     }
1264
1265     if (sg.getSize() > 0)
1266     {
1267       al.addGroup(sg);
1268     }
1269   }
1270
1271   void addRowProperties(AlignmentI al, StringTokenizer st)
1272   {
1273     String label = st.nextToken(), keyValue, key, value;
1274     boolean scaletofit = false, centerlab = false, showalllabs = false;
1275     while (st.hasMoreTokens())
1276     {
1277       keyValue = st.nextToken();
1278       key = keyValue.substring(0, keyValue.indexOf("="));
1279       value = keyValue.substring(keyValue.indexOf("=") + 1);
1280       if (key.equalsIgnoreCase("scaletofit"))
1281       {
1282         scaletofit = Boolean.valueOf(value).booleanValue();
1283       }
1284       if (key.equalsIgnoreCase("showalllabs"))
1285       {
1286         showalllabs = Boolean.valueOf(value).booleanValue();
1287       }
1288       if (key.equalsIgnoreCase("centrelabs"))
1289       {
1290         centerlab = Boolean.valueOf(value).booleanValue();
1291       }
1292       AlignmentAnnotation[] alr = al.getAlignmentAnnotation();
1293       if (alr != null)
1294       {
1295         for (int i = 0; i < alr.length; i++)
1296         {
1297           if (alr[i].label.equalsIgnoreCase(label))
1298           {
1299             alr[i].centreColLabels = centerlab;
1300             alr[i].scaleColLabel = scaletofit;
1301             alr[i].showAllColLabels = showalllabs;
1302           }
1303         }
1304       }
1305     }
1306   }
1307
1308   void addProperties(AlignmentI al, StringTokenizer st)
1309   {
1310
1311     // So far we have only added groups to the annotationHash,
1312     // the idea is in the future properties can be added to
1313     // alignments, other annotations etc
1314     if (al.getGroups() == null)
1315     {
1316       return;
1317     }
1318
1319     String name = st.nextToken();
1320     SequenceGroup sg = null;
1321     for (SequenceGroup _sg : al.getGroups())
1322     {
1323       if ((sg = _sg).getName().equals(name))
1324       {
1325         break;
1326       }
1327       else
1328       {
1329         sg = null;
1330       }
1331     }
1332
1333     if (sg != null)
1334     {
1335       String keyValue, key, value;
1336       ColourSchemeI def = sg.cs;
1337       sg.cs = null;
1338       while (st.hasMoreTokens())
1339       {
1340         keyValue = st.nextToken();
1341         key = keyValue.substring(0, keyValue.indexOf("="));
1342         value = keyValue.substring(keyValue.indexOf("=") + 1);
1343
1344         if (key.equalsIgnoreCase("description"))
1345         {
1346           sg.setDescription(value);
1347         }
1348         else if (key.equalsIgnoreCase("colour"))
1349         {
1350           sg.cs = ColourSchemeProperty.getColour(al, value);
1351         }
1352         else if (key.equalsIgnoreCase("pidThreshold"))
1353         {
1354           sg.cs.setThreshold(Integer.parseInt(value), true);
1355
1356         }
1357         else if (key.equalsIgnoreCase("consThreshold"))
1358         {
1359           sg.cs.setConservationInc(Integer.parseInt(value));
1360           Conservation c = new Conservation("Group",
1361                   ResidueProperties.propHash, 3, sg.getSequences(null),
1362                   sg.getStartRes(), sg.getEndRes() + 1);
1363
1364           c.calculate();
1365           c.verdict(false, 25); // TODO: refer to conservation percent threshold
1366
1367           sg.cs.setConservation(c);
1368
1369         }
1370         else if (key.equalsIgnoreCase("outlineColour"))
1371         {
1372           sg.setOutlineColour(new UserColourScheme(value).findColour('A'));
1373         }
1374         else if (key.equalsIgnoreCase("displayBoxes"))
1375         {
1376           sg.setDisplayBoxes(Boolean.valueOf(value).booleanValue());
1377         }
1378         else if (key.equalsIgnoreCase("showUnconserved"))
1379         {
1380           sg.setShowNonconserved(Boolean.valueOf(value).booleanValue());
1381         }
1382         else if (key.equalsIgnoreCase("displayText"))
1383         {
1384           sg.setDisplayText(Boolean.valueOf(value).booleanValue());
1385         }
1386         else if (key.equalsIgnoreCase("colourText"))
1387         {
1388           sg.setColourText(Boolean.valueOf(value).booleanValue());
1389         }
1390         else if (key.equalsIgnoreCase("textCol1"))
1391         {
1392           sg.textColour = new UserColourScheme(value).findColour('A');
1393         }
1394         else if (key.equalsIgnoreCase("textCol2"))
1395         {
1396           sg.textColour2 = new UserColourScheme(value).findColour('A');
1397         }
1398         else if (key.equalsIgnoreCase("textColThreshold"))
1399         {
1400           sg.thresholdTextColour = Integer.parseInt(value);
1401         }
1402         else if (key.equalsIgnoreCase("idColour"))
1403         {
1404           // consider warning if colour doesn't resolve to a real colour
1405           sg.setIdColour((def = new UserColourScheme(value))
1406                   .findColour('A'));
1407         }
1408         else if (key.equalsIgnoreCase("hide"))
1409         {
1410           // see bug https://mantis.lifesci.dundee.ac.uk/view.php?id=25847
1411           sg.setHidereps(true);
1412         }
1413         else if (key.equalsIgnoreCase("hidecols"))
1414         {
1415           // see bug https://mantis.lifesci.dundee.ac.uk/view.php?id=25847
1416           sg.setHideCols(true);
1417         }
1418         sg.recalcConservation();
1419       }
1420       if (sg.cs == null)
1421       {
1422         sg.cs = def;
1423       }
1424     }
1425   }
1426
1427   void setBelowAlignment(AlignmentI al, StringTokenizer st)
1428   {
1429     String token;
1430     AlignmentAnnotation aa, ala[] = al.getAlignmentAnnotation();
1431     if (ala == null)
1432     {
1433       System.err
1434               .print("Warning - no annotation to set below for sequence associated annotation:");
1435     }
1436     while (st.hasMoreTokens())
1437     {
1438       token = st.nextToken();
1439       if (ala == null)
1440       {
1441         System.err.print(" " + token);
1442       }
1443       else
1444       {
1445         for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1446         {
1447           aa = al.getAlignmentAnnotation()[i];
1448           if (aa.sequenceRef == refSeq && aa.label.equals(token))
1449           {
1450             aa.belowAlignment = true;
1451           }
1452         }
1453       }
1454     }
1455     if (ala == null)
1456     {
1457       System.err.print("\n");
1458     }
1459   }
1460
1461   void addAlignmentDetails(AlignmentI al, StringTokenizer st)
1462   {
1463     String keyValue, key, value;
1464     while (st.hasMoreTokens())
1465     {
1466       keyValue = st.nextToken();
1467       key = keyValue.substring(0, keyValue.indexOf("="));
1468       value = keyValue.substring(keyValue.indexOf("=") + 1);
1469       al.setProperty(key, value);
1470     }
1471   }
1472
1473   /**
1474    * Write annotations as a CSV file of the form 'label, value, value, ...' for
1475    * each row.
1476    * 
1477    * @param annotations
1478    * @return CSV file as a string.
1479    */
1480   public String printCSVAnnotations(AlignmentAnnotation[] annotations)
1481   {
1482     StringBuffer sp = new StringBuffer();
1483     for (int i = 0; i < annotations.length; i++)
1484     {
1485       String atos = annotations[i].toString();
1486       int p = 0;
1487       do
1488       {
1489         int cp = atos.indexOf("\n", p);
1490         sp.append(annotations[i].label);
1491         sp.append(",");
1492         if (cp > p)
1493         {
1494           sp.append(atos.substring(p, cp + 1));
1495         }
1496         else
1497         {
1498           sp.append(atos.substring(p));
1499           sp.append(newline);
1500         }
1501         p = cp + 1;
1502       } while (p > 0);
1503     }
1504     return sp.toString();
1505   }
1506 }