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