Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 14:43:25 GMT
  2. Package jalview.io

File AnnotationFile.java

 

Coverage histogram

../../img/srcFileCovDistChart8.png
20% of files have more coverage

Code metrics

468
839
32
2
2,063
1,711
337
0.4
26.22
16
10.53

Classes

Class Line # Actions
AnnotationFile 57 835 336
0.7878560478.8%
AnnotationFile.ViewDef 143 4 1
1.0100%
 

Contributing tests

This file is covered by 16 tests. .

Source view

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