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