Clover icon

Coverage Report

  1. Project Clover database Tue Nov 4 2025 11:21:43 GMT
  2. Package jalview.io

File AnnotationFile.java

 

Coverage histogram

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

Code metrics

466
835
31
2
2,040
1,701
334
0.4
26.94
15.5
10.77

Classes

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