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