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