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