Clover icon

Coverage Report

  1. Project Clover database Tue Mar 10 2026 14:58:44 GMT
  2. Package jalview.io

File AnnotationFile.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
0% 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.00%
AnnotationFile.ViewDef 143 4 1
0.00%
 

Contributing tests

No tests hitting this source file were found.

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  0 toggle static
93    {
94  0 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  0 toggle public AnnotationFile()
110    {
111  0 init();
112    }
113   
 
114  0 toggle private void init()
115    {
116  0 text = new StringBuffer(JALVIEW_ANNOTATION + newline + "# Created: "
117    + new java.util.Date() + newline + newline);
118  0 refSeq = null;
119  0 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  0 toggle public ViewDef(String vname, HiddenSequences hseqs, HiddenColumns hcols,
155    Hashtable hRepSeqs)
156    {
157  0 this.viewname = vname;
158  0 this.hidseqs = hseqs;
159  0 this.hiddencols = hcols;
160  0 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  0 toggle public String printAnnotations(AlignmentAnnotation[] annotations,
175    List<SequenceGroup> list, Hashtable properties, HiddenColumns cs,
176    AlignmentI al, ViewDef view)
177    {
178  0 if (view != null)
179    {
180  0 if (view.viewname != null)
181    {
182  0 text.append("VIEW_DEF\t" + view.viewname + "\n");
183    }
184  0 if (list == null)
185    {
186    // list = view.visibleGroups;
187    }
188  0 if (cs == null)
189    {
190  0 cs = view.hiddencols;
191    }
192  0 if (al == null)
193    {
194    // add hidden rep sequences.
195    }
196    }
197    // first target - store and restore all settings for a view.
198  0 if (al != null && al.hasSeqrep())
199    {
200  0 text.append("VIEW_SETREF\t" + al.getSeqrep().getName() + "\n");
201    }
202  0 if (cs != null && cs.hasHiddenColumns())
203    {
204  0 text.append("VIEW_HIDECOLS\t");
205   
206  0 String regions = cs.regionsToString(",", "-");
207  0 text.append(regions);
208  0 text.append("\n");
209    }
210    // TODO: allow efficient recovery of annotation data shown in several
211    // different views
212  0 if (annotations != null)
213    {
214  0 boolean oneColour = true;
215  0 AlignmentAnnotation row;
216  0 String comma;
217  0 SequenceI refSeq = null;
218  0 SequenceGroup refGroup = null;
219  0 String calcIdString = ""; // CALC_ID statement for the current annotation
220  0 StringBuffer colours = new StringBuffer();
221  0 StringBuffer graphLine = new StringBuffer();
222  0 StringBuffer rowprops = new StringBuffer();
223  0 Hashtable<Integer, String> graphGroup = new Hashtable<>();
224  0 Hashtable<Integer, Object[]> graphGroup_refs = new Hashtable<>();
225  0 BitSet graphGroupSeen = new BitSet();
226   
227  0 java.awt.Color color;
228   
229  0 for (int i = 0; i < annotations.length; i++)
230    {
231  0 row = annotations[i];
232   
233  0 if (!row.visible && !row.hasScore() && !(row.graphGroup > -1
234    && graphGroupSeen.get(row.graphGroup)))
235    {
236  0 continue;
237    }
238   
239  0 color = null;
240  0 oneColour = true;
241   
242    // mark any sequence references for the row
243  0 writeSequence_Ref(refSeq, row.sequenceRef);
244  0 refSeq = row.sequenceRef;
245    // mark any group references for the row
246  0 writeGroup_Ref(refGroup, row.groupRef);
247  0 refGroup = row.groupRef;
248   
249  0 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  0 for (int j = 0; row.annotations != null
253    && j < row.annotations.length
254    && (!hasGlyphs || !hasLabels || !hasValues); j++)
255    {
256  0 if (row.annotations[j] != null)
257    {
258  0 hasLabels |= (row.annotations[j].displayCharacter != null
259    && row.annotations[j].displayCharacter.length() > 0
260    && !row.annotations[j].displayCharacter.equals(" "));
261  0 hasGlyphs |= (row.annotations[j].secondaryStructure != 0
262    && row.annotations[j].secondaryStructure != ' ');
263  0 hasValues |= (!Float.isNaN(row.annotations[j].value)); // NaNs can't
264    // be
265    // rendered..
266  0 hasText |= (row.annotations[j].description != null
267    && row.annotations[j].description.length() > 0);
268    }
269    }
270   
271    // output CalcId if need be
272  0 if (row.getCalcId() != null && row.getCalcId().trim().length() > 0)
273    {
274  0 calcIdString = CALCID + "\t" + row.getCalcId() + "\t";
275  0 text.append(calcIdString);
276    }
277    else
278    {
279  0 calcIdString = "";
280    }
281   
282  0 if (row.graph == AlignmentAnnotation.NO_GRAPH)
283    {
284  0 text.append("NO_GRAPH\t");
285  0 hasValues = false; // only secondary structure
286    // hasLabels = false; // and annotation description string.
287    }
288    else
289    {
290  0 if (row.graph == AlignmentAnnotation.BAR_GRAPH)
291    {
292  0 text.append("BAR_GRAPH\t");
293  0 hasGlyphs = false; // no secondary structure
294   
295    }
296  0 else if (row.graph == AlignmentAnnotation.LINE_GRAPH)
297    {
298  0 hasGlyphs = false; // no secondary structure
299  0 text.append("LINE_GRAPH\t");
300    }
301    // Graphs have Thresholds
302   
303  0 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  0 if (row.graphGroup > -1)
321    {
322  0 graphGroupSeen.set(row.graphGroup);
323  0 Integer key = Integer.valueOf(row.graphGroup);
324  0 if (graphGroup.containsKey(key))
325    {
326  0 graphGroup.put(key, graphGroup.get(key) + "\t" + row.label);
327   
328    }
329    else
330    {
331  0 graphGroup_refs.put(key, new Object[] { refSeq, refGroup });
332  0 graphGroup.put(key, row.label);
333    }
334    }
335    }
336   
337  0 text.append(row.label + "\t");
338  0 if (row.description != null)
339    {
340  0 text.append(row.description + "\t");
341    }
342  0 for (int j = 0; row.annotations != null
343    && j < row.annotations.length; j++)
344    {
345  0 if (refSeq != null
346    && jalview.util.Comparison.isGap(refSeq.getCharAt(j)))
347    {
348  0 continue;
349    }
350   
351  0 if (row.annotations[j] != null)
352    {
353  0 comma = "";
354  0 if (hasGlyphs) // could be also hasGlyphs || ...
355    {
356   
357  0 text.append(comma);
358  0 if (row.annotations[j].secondaryStructure != ' ')
359    {
360    // only write out the field if its not whitespace.
361  0 text.append(row.annotations[j].secondaryStructure);
362    }
363  0 comma = ",";
364    }
365  0 if (hasValues)
366    {
367  0 if (!Float.isNaN(row.annotations[j].value))
368    {
369  0 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  0 comma = ",";
378    }
379  0 if (hasLabels)
380    {
381    // TODO: labels are emitted after values for bar graphs.
382  0 if // empty labels are allowed, so
383  0 (row.annotations[j].displayCharacter != null
384    && row.annotations[j].displayCharacter.length() > 0
385    && !row.annotations[j].displayCharacter.equals(" "))
386    {
387  0 text.append(comma + row.annotations[j].displayCharacter);
388  0 comma = ",";
389    }
390    }
391  0 if (hasText)
392    {
393  0 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  0 text.append(comma + row.annotations[j].description);
399  0 comma = ",";
400    }
401    }
402  0 if (color != null && !color.equals(row.annotations[j].colour))
403    {
404  0 oneColour = false;
405    }
406   
407  0 color = row.annotations[j].colour;
408   
409  0 if (row.annotations[j].colour != null
410    && row.annotations[j].colour != java.awt.Color.black)
411    {
412  0 text.append(comma + "[" + jalview.util.Format
413    .getHexString(row.annotations[j].colour) + "]");
414  0 comma = ",";
415    }
416    }
417  0 text.append("|");
418    }
419   
420  0 if (row.hasScore())
421    {
422  0 text.append("\t" + row.score);
423    }
424   
425    // Add annotation properties
426  0 Collection<String> annotProperties = row.getProperties();
427  0 for (String annotProperty : annotProperties)
428    {
429  0 String propertyValue = row.getProperty(annotProperty);
430  0 if (propertyValue != null && propertyValue.trim().length() > 0)
431    {
432  0 text.append("\t" + annotProperty + "=" + propertyValue);
433    }
434    }
435  0 if (row.getNoOfSequencesIncluded()!=-1)
436    {
437  0 text.append("\t"+NO_OF_SEQUENCES+"="+row.getNoOfSequencesIncluded());
438    }
439   
440  0 if (row.getNoOfTracksIncluded()!=-1)
441    {
442  0 text.append("\t"+NO_OF_TRACKS+"="+row.getNoOfTracksIncluded());
443    }
444   
445  0 text.append(newline);
446   
447  0 if (color != null && !color.equals(java.awt.Color.black)
448    && oneColour)
449    {
450  0 colours.append(calcIdString);
451  0 colours.append("COLOUR\t");
452  0 colours.append(row.label);
453  0 colours.append("\t");
454  0 colours.append(jalview.util.Format.getHexString(color));
455  0 colours.append(newline);
456    }
457  0 if (row.scaleColLabel || row.showAllColLabels
458    || row.centreColLabels)
459    {
460  0 rowprops.append(calcIdString);
461  0 rowprops.append("ROWPROPERTIES\t");
462  0 rowprops.append(row.label);
463  0 rowprops.append("\tscaletofit=");
464  0 rowprops.append(row.scaleColLabel);
465  0 rowprops.append("\tshowalllabs=");
466  0 rowprops.append(row.showAllColLabels);
467  0 rowprops.append("\tcentrelabs=");
468  0 rowprops.append(row.centreColLabels);
469  0 rowprops.append(newline);
470  0 text.append(rowprops);
471  0 rowprops.setLength(0);
472    }
473  0 if (graphLine.length() > 0)
474    {
475  0 text.append(graphLine.toString());
476  0 graphLine.setLength(0);
477    }
478    }
479   
480  0 text.append(newline);
481   
482  0 text.append(colours.toString());
483  0 if (graphGroup.size() > 0)
484    {
485  0 SequenceI oldRefSeq = refSeq;
486  0 SequenceGroup oldRefGroup = refGroup;
487  0 for (Map.Entry<Integer, String> combine_statement : graphGroup
488    .entrySet())
489    {
490  0 Object[] seqRefAndGroup = graphGroup_refs
491    .get(combine_statement.getKey());
492   
493  0 writeSequence_Ref(refSeq, (SequenceI) seqRefAndGroup[0]);
494  0 refSeq = (SequenceI) seqRefAndGroup[0];
495   
496  0 writeGroup_Ref(refGroup, (SequenceGroup) seqRefAndGroup[1]);
497  0 refGroup = (SequenceGroup) seqRefAndGroup[1];
498  0 text.append("COMBINE\t");
499  0 text.append(combine_statement.getValue());
500  0 text.append(newline);
501    }
502  0 writeSequence_Ref(refSeq, oldRefSeq);
503  0 refSeq = oldRefSeq;
504   
505  0 writeGroup_Ref(refGroup, oldRefGroup);
506  0 refGroup = oldRefGroup;
507    }
508  0 text.append(rowprops.toString());
509    }
510   
511  0 if (list != null)
512    {
513  0 printGroups(list);
514    }
515   
516  0 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  0 return text.toString();
537    }
538   
 
539  0 toggle private Object writeGroup_Ref(SequenceGroup refGroup,
540    SequenceGroup next_refGroup)
541    {
542  0 if (next_refGroup == null)
543    {
544   
545  0 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  0 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  0 toggle private boolean writeSequence_Ref(SequenceI refSeq, SequenceI next_refSeq)
569    {
570   
571  0 if (next_refSeq == null)
572    {
573  0 if (refSeq != null)
574    {
575  0 text.append(newline);
576  0 text.append("SEQUENCE_REF\t");
577  0 text.append("ALIGNMENT");
578  0 text.append(newline);
579  0 return true;
580    }
581    }
582    else
583    {
584  0 if (refSeq == null || refSeq != next_refSeq)
585    {
586  0 text.append(newline);
587  0 text.append("SEQUENCE_REF\t");
588  0 text.append(next_refSeq.getName());
589  0 text.append(newline);
590  0 return true;
591    }
592    }
593  0 return false;
594    }
595   
 
596  0 toggle protected void printGroups(List<SequenceGroup> list)
597    {
598  0 SequenceI seqrep = null;
599  0 for (SequenceGroup sg : list)
600    {
601  0 if (!sg.hasSeqrep())
602    {
603  0 text.append("SEQUENCE_GROUP\t" + sg.getName() + "\t"
604    + (sg.getStartRes() + 1) + "\t" + (sg.getEndRes() + 1)
605    + "\t" + "-1\t");
606  0 seqrep = null;
607    }
608    else
609    {
610  0 seqrep = sg.getSeqrep();
611  0 text.append("SEQUENCE_REF\t");
612  0 text.append(seqrep.getName());
613  0 text.append(newline);
614  0 text.append("SEQUENCE_GROUP\t");
615  0 text.append(sg.getName());
616  0 text.append("\t");
617  0 text.append((seqrep.findPosition(sg.getStartRes())));
618  0 text.append("\t");
619  0 text.append((seqrep.findPosition(sg.getEndRes())));
620  0 text.append("\t");
621  0 text.append("-1\t");
622    }
623  0 for (int s = 0; s < sg.getSize(); s++)
624    {
625  0 text.append(sg.getSequenceAt(s).getName());
626  0 text.append("\t");
627    }
628  0 text.append(newline);
629  0 text.append("PROPERTIES\t");
630  0 text.append(sg.getName());
631  0 text.append("\t");
632   
633  0 if (sg.getDescription() != null)
634    {
635  0 text.append("description=");
636  0 text.append(sg.getDescription());
637  0 text.append("\t");
638    }
639  0 if (sg.cs != null)
640    {
641  0 text.append("colour=");
642  0 text.append(ColourSchemeProperty
643    .getColourName(sg.cs.getColourScheme()));
644  0 text.append("\t");
645  0 if (sg.cs.getThreshold() != 0)
646    {
647  0 text.append("pidThreshold=");
648  0 text.append(sg.cs.getThreshold());
649    }
650  0 if (sg.cs.isConsensusSecondaryStructureColouring())
651    {
652  0 text.append("secondaryStructureConservationThreshold=");
653  0 text.append(sg.cs.getConsensusSecondaryStructureThreshold());
654  0 text.append("\t");
655    }
656  0 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  0 text.append("outlineColour=");
664  0 text.append(jalview.util.Format.getHexString(sg.getOutlineColour()));
665  0 text.append("\t");
666   
667  0 text.append("displayBoxes=");
668  0 text.append(sg.getDisplayBoxes());
669  0 text.append("\t");
670  0 text.append("displayText=");
671  0 text.append(sg.getDisplayText());
672  0 text.append("\t");
673  0 text.append("colourText=");
674  0 text.append(sg.getColourText());
675  0 text.append("\t");
676  0 text.append("showUnconserved=");
677  0 text.append(sg.getShowNonconserved());
678  0 text.append("\t");
679  0 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  0 if (sg.textColour2 != java.awt.Color.white)
686    {
687  0 text.append("textCol2=");
688  0 text.append(jalview.util.Format.getHexString(sg.textColour2));
689  0 text.append("\t");
690    }
691  0 if (sg.thresholdTextColour != 0)
692    {
693  0 text.append("textColThreshold=");
694  0 text.append(sg.thresholdTextColour);
695  0 text.append("\t");
696    }
697  0 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  0 if (sg.isHidereps())
704    {
705  0 text.append("hide=true\t");
706    }
707  0 if (sg.isHideCols())
708    {
709  0 text.append("hidecols=true\t");
710    }
711  0 if (seqrep != null)
712    {
713    // terminate the last line and clear the sequence ref for the group
714  0 text.append(newline);
715  0 text.append("SEQUENCE_REF");
716    }
717  0 text.append(newline);
718  0 text.append(newline);
719   
720    }
721    }
722   
 
723  0 toggle public boolean annotateAlignmentView(AlignViewportI viewport, Object file,
724    DataSourceType protocol)
725    {
726  0 ColumnSelection colSel = viewport.getColumnSelection();
727  0 HiddenColumns hidden = viewport.getAlignment().getHiddenColumns();
728  0 if (colSel == null)
729    {
730  0 colSel = new ColumnSelection();
731    }
732  0 if (hidden == null)
733    {
734  0 hidden = new HiddenColumns();
735    }
736  0 boolean rslt = readAnnotationFile(viewport.getAlignment(), hidden, file,
737    protocol);
738  0 if (rslt && (colSel.hasSelectedColumns() || hidden.hasHiddenColumns()))
739    {
740  0 viewport.setColumnSelection(colSel);
741  0 viewport.getAlignment().setHiddenColumns(hidden);
742    }
743   
744  0 return rslt;
745    }
746   
 
747  0 toggle public boolean readAnnotationFile(AlignmentI al, String file,
748    DataSourceType sourceType)
749    {
750  0 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  0 toggle public boolean readAnnotationFile(AlignmentI al, HiddenColumns hidden,
769    Object file, DataSourceType sourceType)
770    {
771  0 BufferedReader in = null;
772  0 try
773    {
774  0 in = new FileParse().getBufferedReader(file, sourceType);
775  0 if (in != null)
776    {
777  0 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  0 toggle public boolean parseAnnotationFrom(AlignmentI al, HiddenColumns hidden,
795    BufferedReader in) throws Exception
796    {
797  0 nlinesread = 0;
798  0 ArrayList<Object[]> combineAnnotation_calls = new ArrayList<>();
799  0 ArrayList<Object[]> deferredAnnotation_calls = new ArrayList<>();
800  0 boolean modified = false;
801  0 String groupRef = null;
802  0 Hashtable groupRefRows = new Hashtable();
803   
804  0 Hashtable autoAnnots = new Hashtable();
805    {
806  0 String line, label, description, token;
807  0 int graphStyle, index;
808  0 int refSeqIndex = 1;
809  0 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  0 boolean overrideAutoAnnot = false;
813  0 if (al.getAlignmentAnnotation() != null)
814    {
815  0 existingAnnotations = al.getAlignmentAnnotation().length;
816  0 if (existingAnnotations > 0)
817    {
818  0 AlignmentAnnotation[] aa = al.getAlignmentAnnotation();
819  0 for (int aai = 0; aai < aa.length; aai++)
820    {
821  0 if (aa[aai].autoCalculated)
822    {
823    // make a note of the name and description
824  0 autoAnnots.put(
825    autoAnnotsKey(aa[aai], aa[aai].sequenceRef,
826  0 (aa[aai].groupRef == null ? null
827    : aa[aai].groupRef.getName())),
828    Integer.valueOf(1));
829    }
830    }
831    }
832    }
833   
834  0 int alWidth = al.getWidth();
835   
836  0 StringTokenizer st;
837  0 Annotation[] annotations;
838  0 AlignmentAnnotation annotation = null;
839   
840    // First confirm this is an Annotation file
841  0 boolean jvAnnotationFile = false;
842  0 while ((line = in.readLine()) != null)
843    {
844  0 nlinesread++;
845  0 lastread = new String(line);
846  0 if (line.indexOf("#") == 0)
847    {
848  0 continue;
849    }
850   
851  0 if (line.indexOf(JALVIEW_ANNOTATION) > -1)
852    {
853  0 jvAnnotationFile = true;
854  0 break;
855    }
856    }
857   
858  0 if (!jvAnnotationFile)
859    {
860  0 in.close();
861  0 return false;
862    }
863   
864  0 while ((line = in.readLine()) != null)
865    {
866  0 nlinesread++;
867  0 lastread = new String(line);
868  0 if (line.indexOf("#") == 0 || line.indexOf(JALVIEW_ANNOTATION) > -1
869    || line.length() == 0)
870    {
871  0 continue;
872    }
873   
874  0 st = new StringTokenizer(line, "\t");
875  0 token = st.nextToken();
876  0 String calcId = "";
877  0 if (token.equalsIgnoreCase(CALCID))
878    {
879  0 if (st.hasMoreTokens())
880    {
881  0 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  0 if (!isAnnotationKeyword(token))
891    {
892  0 calcId = token;
893  0 if (st.hasMoreTokens())
894    {
895  0 token = st.nextToken();
896    }
897    else
898    {
899  0 continue;
900    }
901    }
902    }
903   
904  0 if (token.equalsIgnoreCase("COLOUR"))
905    {
906    // TODO: use graduated colour def'n here too
907  0 colourAnnotations(al, st.nextToken(), calcId, st.nextToken());
908  0 modified = true;
909  0 continue;
910    }
911   
912  0 else if (token.equalsIgnoreCase(COMBINE))
913    {
914    // keep a record of current state and resolve groupRef at end
915  0 combineAnnotation_calls
916    .add(new Object[]
917    { st, refSeq, groupRef });
918  0 modified = true;
919  0 continue;
920    }
921  0 else if (token.equalsIgnoreCase("ROWPROPERTIES"))
922    {
923  0 addRowProperties(al, st, calcId);
924  0 modified = true;
925  0 continue;
926    }
927  0 else if (token.equalsIgnoreCase(GRAPHLINE))
928    {
929    // resolve at end
930  0 deferredAnnotation_calls
931    .add(new Object[]
932    { GRAPHLINE, st, refSeq, groupRef, calcId });
933  0 modified = true;
934  0 continue;
935    }
936   
937  0 else if (token.equalsIgnoreCase("SEQUENCE_REF"))
938    {
939  0 if (st.hasMoreTokens())
940    {
941  0 refSeq = al.findName(refSeqId = st.nextToken());
942  0 if (refSeq == null)
943    {
944  0 refSeqId = null;
945    }
946  0 try
947    {
948  0 refSeqIndex = Integer.parseInt(st.nextToken());
949  0 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  0 refSeqIndex = 1;
958    }
959    }
960    else
961    {
962  0 refSeq = null;
963  0 refSeqId = null;
964    }
965  0 continue;
966    }
967  0 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  0 else if (token.equalsIgnoreCase("SEQUENCE_GROUP"))
990    {
991  0 addGroup(al, st);
992  0 modified = true;
993  0 continue;
994    }
995   
996  0 else if (token.equalsIgnoreCase("PROPERTIES"))
997    {
998  0 addProperties(al, st);
999  0 modified = true;
1000  0 continue;
1001    }
1002   
1003  0 else if (token.equalsIgnoreCase("BELOW_ALIGNMENT"))
1004    {
1005  0 setBelowAlignment(al, st, calcId);
1006  0 modified = true;
1007  0 continue;
1008    }
1009  0 else if (token.equalsIgnoreCase("ALIGNMENT"))
1010    {
1011  0 addAlignmentDetails(al, st);
1012  0 modified = true;
1013  0 continue;
1014    }
1015    // else if (token.equalsIgnoreCase("VIEW_DEF"))
1016    // {
1017    // addOrSetView(al,st);
1018    // modified = true;
1019    // continue;
1020    // }
1021  0 else if (token.equalsIgnoreCase("VIEW_SETREF"))
1022    {
1023  0 if (refSeq != null)
1024    {
1025  0 al.setSeqrep(refSeq);
1026    }
1027  0 modified = true;
1028  0 continue;
1029    }
1030  0 else if (token.equalsIgnoreCase("VIEW_HIDECOLS"))
1031    {
1032  0 if (st.hasMoreTokens())
1033    {
1034  0 if (hidden == null)
1035    {
1036  0 hidden = new HiddenColumns();
1037    }
1038  0 parseHideCols(hidden, st.nextToken());
1039    }
1040  0 modified = true;
1041  0 continue;
1042    }
1043  0 else if (token.equalsIgnoreCase("HIDE_INSERTIONS"))
1044    {
1045  0 SequenceI sr = refSeq == null ? al.getSeqrep() : refSeq;
1046  0 if (sr == null)
1047    {
1048  0 sr = al.getSequenceAt(0);
1049    }
1050  0 if (sr != null)
1051    {
1052  0 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  0 hidden.hideList(sr.getInsertions());
1062    }
1063    }
1064  0 modified = true;
1065  0 continue;
1066    }
1067   
1068    // Parse out the annotation row
1069  0 graphStyle = AlignmentAnnotation.getGraphValueFromString(token);
1070  0 label = st.nextToken();
1071   
1072  0 index = 0;
1073  0 annotations = new Annotation[alWidth];
1074  0 description = null;
1075  0 HashMap<String, String> annotationProperties = new HashMap<String, String>(); // Stores
1076    // data
1077    // properties
1078   
1079  0 float score = Float.NaN;
1080   
1081  0 if (st.hasMoreTokens())
1082    {
1083  0 line = st.nextToken();
1084   
1085  0 if (line.indexOf("|") == -1)
1086    {
1087  0 description = line;
1088  0 if (st.hasMoreTokens())
1089    {
1090  0 line = st.nextToken();
1091    }
1092    }
1093   
1094  0 int scoreCount = 0;
1095  0 if (st.hasMoreTokens())
1096    {
1097  0 while (st.hasMoreTokens())
1098    {
1099  0 token = st.nextToken();
1100   
1101    // Check if the token is a float. If yes, it is score
1102  0 if (StringUtils.isValidFloat(token))
1103    {
1104  0 score = Float.parseFloat(token);
1105  0 scoreCount++;
1106    }
1107    else
1108    {
1109    // Not a score. Check if it is a property value
1110  0 if (isPropertyValueToken(token))
1111    {
1112  0 annotationProperties.put(
1113    token.substring(0, token.indexOf('=')),
1114    token.substring(token.indexOf('=') + 1));
1115    }
1116   
1117  0 break;
1118    }
1119    }
1120   
1121  0 if (scoreCount > 1)
1122    {
1123  0 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  0 while (st.hasMoreTokens())
1131    {
1132  0 token = st.nextToken();
1133   
1134  0 if (isPropertyValueToken(token))
1135    {
1136  0 annotationProperties.put(
1137    token.substring(0, token.indexOf('=')),
1138    token.substring(token.indexOf('=') + 1));
1139    }
1140    }
1141    }
1142   
1143  0 st = new StringTokenizer(line, "|", true);
1144   
1145  0 boolean emptyColumn = true;
1146  0 boolean onlyOneElement = (st.countTokens() == 1);
1147   
1148  0 while (st.hasMoreElements() && index < alWidth)
1149    {
1150  0 token = st.nextToken().trim();
1151   
1152  0 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  0 if (token.equals("|"))
1164    {
1165  0 if (emptyColumn)
1166    {
1167  0 index++;
1168    }
1169   
1170  0 emptyColumn = true;
1171    }
1172    else
1173    {
1174  0 annotations[index++] = parseAnnotation(token, graphStyle);
1175  0 emptyColumn = false;
1176    }
1177    }
1178   
1179    }
1180   
1181  0 annotation = new AlignmentAnnotation(label, description,
1182  0 (index == 0) ? null : annotations, 0, 0, graphStyle);
1183   
1184    // remove the 'number of' fields if present
1185  0 String no_of = annotationProperties.get(NO_OF_SEQUENCES);
1186  0 if (no_of!=null)
1187    {
1188  0 try {
1189  0 annotation.setNoOfSequencesIncluded(Long.valueOf(no_of));
1190  0 annotationProperties.remove(NO_OF_SEQUENCES);
1191    } catch (Exception x)
1192    {
1193    // ignore parse errors and all else
1194    }
1195    }
1196   
1197  0 no_of = annotationProperties.get(NO_OF_TRACKS);
1198  0 if (no_of!=null)
1199    {
1200  0 try {
1201  0 annotation.setNoOfTracksIncluded(Long.valueOf(no_of));
1202  0 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  0 for (String key : annotationProperties.keySet())
1211    {
1212  0 annotation.setProperty(key, annotationProperties.get(key));
1213    }
1214   
1215    // Set calcid to the annotation
1216  0 if (calcId.length() > 0)
1217    {
1218  0 annotation.setCalcId(calcId);
1219  0 } else if (defaultCalcId!=null && defaultCalcId.length()>0) {
1220  0 annotation.setCalcId(defaultCalcId);
1221    }
1222  0 annotation.score = score;
1223  0 if (!overrideAutoAnnot && autoAnnots
1224    .containsKey(autoAnnotsKey(annotation, refSeq, groupRef)))
1225    {
1226    // skip - we've already got an automatic annotation of this type.
1227  0 continue;
1228    }
1229    // otherwise add it!
1230  0 if (refSeq != null)
1231    {
1232   
1233  0 annotation.belowAlignment = false;
1234    // make a copy of refSeq so we can find other matches in the alignment
1235  0 SequenceI referedSeq = refSeq;
1236  0 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  0 AlignmentAnnotation ann = new AlignmentAnnotation(annotation);
1242  0 annotation.createSequenceMapping(referedSeq, refSeqIndex,
1243    false);
1244  0 annotation.adjustForAlignment();
1245  0 referedSeq.addAlignmentAnnotation(annotation);
1246  0 al.addAnnotation(annotation);
1247  0 al.setAnnotationIndex(annotation,
1248    al.getAlignmentAnnotation().length - existingAnnotations
1249    - 1);
1250  0 if (groupRef != null)
1251    {
1252  0 ((Vector) groupRefRows.get(groupRef)).addElement(annotation);
1253    }
1254  0 AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
1255    annotation, label, calcId, referedSeq);
1256    // and recover our virgin copy to use again if necessary.
1257  0 annotation = ann;
1258   
1259  ? } while (refSeqId != null && (referedSeq = al.findName(referedSeq,
1260    refSeqId, true)) != null);
1261    }
1262    else
1263    {
1264  0 al.addAnnotation(annotation);
1265  0 al.setAnnotationIndex(annotation,
1266    al.getAlignmentAnnotation().length - existingAnnotations
1267    - 1);
1268  0 if (groupRef != null)
1269    {
1270  0 ((Vector) groupRefRows.get(groupRef)).addElement(annotation);
1271    }
1272    }
1273    // and set modification flag
1274  0 modified = true;
1275    }
1276    // Resolve the groupRefs
1277  0 Hashtable<String, SequenceGroup> groupRefLookup = new Hashtable<>();
1278  0 Enumeration en = groupRefRows.keys();
1279   
1280  0 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  0 for (Object[] _deferred_args : deferredAnnotation_calls)
1319    {
1320  0 if (_deferred_args[0] == GRAPHLINE)
1321    {
1322  0 addLine(al, (StringTokenizer) _deferred_args[1], // st
1323    (SequenceI) _deferred_args[2], // refSeq
1324  0 (_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  0 int combinecount = 0;
1341  0 for (Object[] _combine_args : combineAnnotation_calls)
1342    {
1343  0 combineAnnotations(al, ++combinecount,
1344    (StringTokenizer) _combine_args[0], // st
1345    (SequenceI) _combine_args[1], // refSeq
1346  0 (_combine_args[2] == null) ? null
1347    : groupRefLookup.get(_combine_args[2]) // the reference
1348    // group,
1349    // or null
1350    );
1351    }
1352    }
1353  0 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  0 toggle boolean isPropertyValueToken(String token)
1365    {
1366  0 int eq = token.indexOf('=');
1367  0 if (eq > 0 && eq < token.length() - 1)
1368    {
1369  0 String key = token.substring(0, token.indexOf('='));
1370  0 String value = token.substring(token.indexOf('=') + 1).trim();
1371  0 boolean isValidKey = StringUtils
1372    .isStartsWithAlphabetOrUnderScore(key);
1373   
1374  0 return isValidKey && !value.isEmpty();
1375    }
1376  0 return false;
1377    }
1378   
 
1379  0 toggle private void parseHideCols(HiddenColumns hidden, String nextToken)
1380    {
1381  0 StringTokenizer inval = new StringTokenizer(nextToken, ",");
1382  0 while (inval.hasMoreTokens())
1383    {
1384  0 String range = inval.nextToken().trim();
1385  0 int from, to = range.indexOf("-");
1386  0 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  0 from = Integer.parseInt(range.substring(0, to));
1397  0 if (to < range.length() - 1)
1398    {
1399  0 to = Integer.parseInt(range.substring(to + 1));
1400    }
1401    else
1402    {
1403  0 to = from;
1404    }
1405  0 if (from > 0 && to >= from)
1406    {
1407  0 hidden.hideColumns(from, to);
1408    }
1409    }
1410    }
1411    }
1412   
 
1413  0 toggle private Object autoAnnotsKey(AlignmentAnnotation annotation,
1414    SequenceI refSeq, String groupRef)
1415    {
1416  0 return annotation.graph + "\t" + annotation.label + "\t"
1417    + annotation.description + "\t"
1418  0 + (refSeq != null ? refSeq.getDisplayId(true) : "");
1419    }
1420   
 
1421  0 toggle Annotation parseAnnotation(String string, int graphStyle)
1422    {
1423    // don't do the glyph test if we don't want secondary structure
1424  0 boolean hasSymbols = (graphStyle == AlignmentAnnotation.NO_GRAPH);
1425  0 String desc = null, displayChar = null;
1426  0 char ss = ' '; // secondaryStructure
1427  0 float value = 0;
1428  0 boolean parsedValue = false, dcset = false;
1429   
1430    // find colour here
1431  0 Color colour = null;
1432  0 int i = string.indexOf("[");
1433  0 int j = string.indexOf("]");
1434  0 if (i > -1 && j > -1)
1435    {
1436  0 colour = ColorUtils.parseColourString(string.substring(i + 1, j));
1437  0 if (i > 0 && string.charAt(i - 1) == ',')
1438    {
1439    // clip the preceding comma as well
1440  0 i--;
1441    }
1442  0 string = string.substring(0, i) + string.substring(j + 1);
1443    }
1444   
1445  0 StringTokenizer st = new StringTokenizer(string, ",", true);
1446  0 String token;
1447  0 boolean seenContent = false;
1448  0 int pass = 0;
1449  0 while (st.hasMoreTokens())
1450    {
1451  0 pass++;
1452  0 token = st.nextToken().trim();
1453  0 if (token.equals(","))
1454    {
1455  0 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  0 seenContent = false;
1462  0 continue;
1463    }
1464    else
1465    {
1466  0 seenContent = true;
1467    }
1468   
1469  0 if (!parsedValue)
1470    {
1471  0 try
1472    {
1473  0 displayChar = token;
1474    // foo
1475  0 value = Float.valueOf(token).floatValue();
1476  0 parsedValue = true;
1477  0 continue;
1478    } catch (NumberFormatException ex)
1479    {
1480    }
1481    }
1482    else
1483    {
1484  0 if (token.length() == 1)
1485    {
1486  0 displayChar = token;
1487    }
1488    }
1489  0 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  0 ss = token.charAt(0);
1496  0 if (displayChar.equals(token.substring(0, 1)))
1497    {
1498  0 displayChar = "";
1499    }
1500    }
1501  0 else if (desc == null || (parsedValue && pass > 2))
1502    {
1503  0 desc = token;
1504    }
1505   
1506    }
1507    // if (!dcset && string.charAt(string.length() - 1) == ',')
1508    // {
1509    // displayChar = " "; // empty display char symbol.
1510    // }
1511  0 if (displayChar != null && desc != null && desc.length() == 1)
1512    {
1513  0 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  0 if (displayChar.equals(desc))
1523    {
1524    // duplicate label - hangover from the 'robust parser' above
1525  0 desc = null;
1526    }
1527    }
1528    }
1529  0 Annotation anot = new Annotation(displayChar, desc, ss, value);
1530   
1531  0 anot.colour = colour;
1532   
1533  0 return anot;
1534    }
1535   
 
1536  0 toggle void colourAnnotations(AlignmentI al, String label, String calcId,
1537    String colour)
1538    {
1539  0 Color awtColour = ColorUtils.parseColourString(colour);
1540  0 Annotation[] annotations;
1541  0 for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1542    {
1543  0 if (al.getAlignmentAnnotation()[i] != null
1544    && al.getAlignmentAnnotation()[i].labelAndCalcIdMatches(label,
1545    calcId))
1546    {
1547  0 annotations = al.getAlignmentAnnotation()[i].annotations;
1548  0 for (int j = 0; j < annotations.length; j++)
1549    {
1550  0 if (annotations[j] != null)
1551    {
1552  0 annotations[j].colour = awtColour;
1553    }
1554    }
1555    }
1556    }
1557    }
1558   
 
1559  0 toggle void combineAnnotations(AlignmentI al, int combineCount,
1560    StringTokenizer st, SequenceI seqRef, SequenceGroup groupRef)
1561    {
1562  0 String group = st.nextToken();
1563    // First make sure we are not overwriting the graphIndex
1564  0 int graphGroup = 0;
1565  0 if (al.getAlignmentAnnotation() != null)
1566    {
1567  0 for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1568    {
1569  0 AlignmentAnnotation aa = al.getAlignmentAnnotation()[i];
1570   
1571  0 if (aa.graphGroup > graphGroup)
1572    {
1573    // try to number graphGroups in order of occurence.
1574  0 graphGroup = aa.graphGroup + 1;
1575    }
1576  0 if (aa.sequenceRef == seqRef && aa.groupRef == groupRef
1577    && aa.label.equalsIgnoreCase(group))
1578    {
1579  0 if (aa.graphGroup > -1)
1580    {
1581  0 graphGroup = aa.graphGroup;
1582    }
1583    else
1584    {
1585  0 if (graphGroup <= combineCount)
1586    {
1587  0 graphGroup = combineCount + 1;
1588    }
1589  0 aa.graphGroup = graphGroup;
1590    }
1591  0 break;
1592    }
1593    }
1594   
1595    // Now update groups
1596  0 while (st.hasMoreTokens())
1597    {
1598  0 group = st.nextToken();
1599  0 for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1600    {
1601  0 AlignmentAnnotation aa = al.getAlignmentAnnotation()[i];
1602  0 if (aa.sequenceRef == seqRef && aa.groupRef == groupRef
1603    && aa.label.equalsIgnoreCase(group))
1604    {
1605  0 aa.graphGroup = graphGroup;
1606  0 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  0 toggle void addLine(AlignmentI al, StringTokenizer st, SequenceI seqRef,
1619    SequenceGroup groupRef, String calcId)
1620    {
1621  0 String group = st.nextToken();
1622  0 AlignmentAnnotation[] alannot = al.getAlignmentAnnotation();
1623  0 String nextToken = st.nextToken();
1624  0 float value = 0f;
1625  0 try
1626    {
1627  0 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  0 String label = st.hasMoreTokens() ? st.nextToken() : null;
1634  0 Color colour = null;
1635  0 if (st.hasMoreTokens())
1636    {
1637  0 colour = ColorUtils.parseColourString(st.nextToken());
1638    }
1639  0 if (alannot != null)
1640    {
1641  0 for (int i = 0; i < alannot.length; i++)
1642    {
1643  0 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  0 toggle void addGroup(AlignmentI al, StringTokenizer st)
1655    {
1656  0 SequenceGroup sg = new SequenceGroup();
1657  0 sg.setName(st.nextToken());
1658  0 String rng = "";
1659  0 try
1660    {
1661  0 rng = st.nextToken();
1662  0 if (rng.length() > 0 && !rng.startsWith("*"))
1663    {
1664  0 sg.setStartRes(Integer.parseInt(rng) - 1);
1665    }
1666    else
1667    {
1668  0 sg.setStartRes(0);
1669    }
1670  0 rng = st.nextToken();
1671  0 if (rng.length() > 0 && !rng.startsWith("*"))
1672    {
1673  0 sg.setEndRes(Integer.parseInt(rng) - 1);
1674    }
1675    else
1676    {
1677  0 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  0 String index = st.nextToken();
1690  0 if (index.equals("-1"))
1691    {
1692  0 while (st.hasMoreElements())
1693    {
1694  0 sg.addSequence(al.findName(st.nextToken()), false);
1695    }
1696    }
1697    else
1698    {
1699  0 StringTokenizer st2 = new StringTokenizer(index, ",");
1700   
1701  0 while (st2.hasMoreTokens())
1702    {
1703  0 String tmp = st2.nextToken();
1704  0 if (tmp.equals("*"))
1705    {
1706  0 for (int i = 0; i < al.getHeight(); i++)
1707    {
1708  0 sg.addSequence(al.getSequenceAt(i), false);
1709    }
1710    }
1711  0 else if (tmp.indexOf("-") >= 0)
1712    {
1713  0 StringTokenizer st3 = new StringTokenizer(tmp, "-");
1714   
1715  0 int start = (Integer.parseInt(st3.nextToken()));
1716  0 int end = (Integer.parseInt(st3.nextToken()));
1717   
1718  0 if (end > start)
1719    {
1720  0 for (int i = start; i <= end; i++)
1721    {
1722  0 sg.addSequence(al.getSequenceAt(i - 1), false);
1723    }
1724    }
1725    }
1726    else
1727    {
1728  0 sg.addSequence(al.getSequenceAt(Integer.parseInt(tmp) - 1),
1729    false);
1730    }
1731    }
1732    }
1733   
1734  0 if (refSeq != null)
1735    {
1736  0 sg.setStartRes(refSeq.findIndex(sg.getStartRes() + 1) - 1);
1737  0 sg.setEndRes(refSeq.findIndex(sg.getEndRes() + 1) - 1);
1738  0 sg.setSeqrep(refSeq);
1739    }
1740   
1741  0 if (sg.getSize() > 0)
1742    {
1743  0 al.addGroup(sg);
1744    }
1745    }
1746   
 
1747  0 toggle void addRowProperties(AlignmentI al, StringTokenizer st, String calcId)
1748    {
1749  0 String label = st.nextToken(), keyValue, key, value;
1750  0 boolean scaletofit = false, centerlab = false, showalllabs = false;
1751  0 Map<String, String> genprop = new HashMap<String, String>();
1752  0 while (st.hasMoreTokens())
1753    {
1754   
1755  0 keyValue = st.nextToken();
1756  0 key = keyValue.substring(0, keyValue.indexOf("="));
1757  0 value = keyValue.substring(keyValue.indexOf("=") + 1);
1758   
1759  0 if (key.equalsIgnoreCase("scaletofit"))
1760    {
1761  0 scaletofit = Boolean.valueOf(value).booleanValue();
1762    }
1763  0 else if (key.equalsIgnoreCase("showalllabs"))
1764    {
1765  0 showalllabs = Boolean.valueOf(value).booleanValue();
1766    }
1767  0 else if (key.equalsIgnoreCase("centrelabs"))
1768    {
1769  0 centerlab = Boolean.valueOf(value).booleanValue();
1770    }
1771    else
1772    {
1773  0 if (key.length() > 0 && value.length() > 0)
1774    {
1775  0 genprop.put(key, value);
1776    }
1777    }
1778    }
1779    // update all matching rows with these properties
1780  0 AlignmentAnnotation[] alr = al.getAlignmentAnnotation();
1781  0 if (alr != null)
1782    {
1783  0 for (int i = 0; i < alr.length; i++)
1784    {
1785  0 if (alr[i] != null && alr[i].labelAndCalcIdMatches(label, calcId)
1786    && alr[i].sequenceRef == refSeq) // should we also check
1787    // groupRef ?
1788   
1789    {
1790  0 addAnnotProperties(alr[i], centerlab, scaletofit, showalllabs,
1791    genprop);
1792  0 if (refSeq != null)
1793    {
1794  0 AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
1795    alr[i], label, calcId, refSeq);
1796    }
1797   
1798    }
1799    }
1800    }
1801    }
1802   
 
1803  0 toggle private void addAnnotProperties(AlignmentAnnotation alr,
1804    boolean centerlab, boolean scaletofit, boolean showalllabs,
1805    Map<String, String> genprop)
1806    {
1807  0 alr.centreColLabels = centerlab;
1808  0 alr.scaleColLabel = scaletofit;
1809  0 alr.showAllColLabels = showalllabs;
1810  0 for (Map.Entry<String, String> pair : genprop.entrySet())
1811    {
1812  0 alr.setProperty(pair.getKey(), pair.getValue());
1813    }
1814    }
1815   
 
1816  0 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  0 if (al.getGroups() == null)
1823    {
1824  0 return;
1825    }
1826   
1827  0 String name = st.nextToken();
1828   
1829  0 Map<String, String> properties = new HashMap<>();
1830  0 while (st.hasMoreTokens())
1831    {
1832  0 String keyValue = st.nextToken();
1833  0 String key = keyValue.substring(0, keyValue.indexOf("="));
1834  0 String value = keyValue.substring(keyValue.indexOf("=") + 1);
1835  0 properties.put(key, value);
1836    }
1837   
1838  0 for (SequenceGroup sg : al.getGroups())
1839    {
1840  0 if (sg.getName().equals(name))
1841    {
1842  0 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  0 toggle private void addProperties(SequenceGroup sg,
1855    Map<String, String> properties, AlignmentI al)
1856    {
1857  0 ColourSchemeI def = sg.getColourScheme();
1858  0 for (String key : properties.keySet())
1859    {
1860  0 String value = properties.get(key);
1861  0 if (key.equalsIgnoreCase("description"))
1862    {
1863  0 sg.setDescription(value);
1864    }
1865  0 else if (key.equalsIgnoreCase("colour"))
1866    {
1867    // TODO need to notify colourscheme of view reference once it is
1868    // available
1869  0 sg.cs.setColourScheme(
1870    ColourSchemeProperty.getColourScheme(null, al, value));
1871    }
1872  0 else if (key.equalsIgnoreCase("pidThreshold"))
1873    {
1874  0 sg.cs.setThreshold(Integer.parseInt(value), true);
1875   
1876    }
1877  0 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  0 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  0 else if (key.equalsIgnoreCase("outlineColour"))
1898    {
1899  0 sg.setOutlineColour(ColorUtils.parseColourString(value));
1900    }
1901  0 else if (key.equalsIgnoreCase("displayBoxes"))
1902    {
1903  0 sg.setDisplayBoxes(Boolean.valueOf(value).booleanValue());
1904    }
1905  0 else if (key.equalsIgnoreCase("showUnconserved"))
1906    {
1907  0 sg.setShowNonconserved(Boolean.valueOf(value).booleanValue());
1908    }
1909  0 else if (key.equalsIgnoreCase("displayText"))
1910    {
1911  0 sg.setDisplayText(Boolean.valueOf(value).booleanValue());
1912    }
1913  0 else if (key.equalsIgnoreCase("colourText"))
1914    {
1915  0 sg.setColourText(Boolean.valueOf(value).booleanValue());
1916    }
1917  0 else if (key.equalsIgnoreCase("textCol1"))
1918    {
1919  0 sg.textColour = ColorUtils.parseColourString(value);
1920    }
1921  0 else if (key.equalsIgnoreCase("textCol2"))
1922    {
1923  0 sg.textColour2 = ColorUtils.parseColourString(value);
1924    }
1925  0 else if (key.equalsIgnoreCase("textColThreshold"))
1926    {
1927  0 sg.thresholdTextColour = Integer.parseInt(value);
1928    }
1929  0 else if (key.equalsIgnoreCase("idColour"))
1930    {
1931  0 Color idColour = ColorUtils.parseColourString(value);
1932  0 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  0 sg.recalcConservation();
1945    }
1946   
1947  0 if (sg.getColourScheme() == null)
1948    {
1949  0 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  0 toggle void addAlignmentDetails(AlignmentI al, StringTokenizer st)
1990    {
1991  0 String keyValue, key, value;
1992  0 while (st.hasMoreTokens())
1993    {
1994  0 keyValue = st.nextToken();
1995  0 key = keyValue.substring(0, keyValue.indexOf("="));
1996  0 value = keyValue.substring(keyValue.indexOf("=") + 1);
1997  0 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  0 toggle public String printCSVAnnotations(AlignmentAnnotation[] annotations)
2009    {
2010  0 if (annotations == null)
2011    {
2012  0 return "";
2013    }
2014  0 StringBuffer sp = new StringBuffer();
2015  0 for (int i = 0; i < annotations.length; i++)
2016    {
2017  0 String atos = annotations[i].toString();
2018  0 int p = 0;
2019  0 do
2020    {
2021  0 int cp = atos.indexOf("\n", p);
2022  0 sp.append(annotations[i].label);
2023  0 sp.append(",");
2024  0 if (cp > p)
2025    {
2026  0 sp.append(atos.substring(p, cp + 1));
2027    }
2028    else
2029    {
2030  0 sp.append(atos.substring(p));
2031  0 sp.append(newline);
2032    }
2033  0 p = cp + 1;
2034  0 } while (p > 0);
2035    }
2036  0 return sp.toString();
2037    }
2038   
 
2039  0 toggle public String printAnnotationsForView(AlignViewportI viewport)
2040    {
2041  0 return printAnnotations(
2042  0 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  0 toggle boolean isAnnotationKeyword(String token)
2058    {
2059  0 return token != null
2060    && ANNOTATION_KEYWORDS.contains(token.toUpperCase());
2061    }
2062   
2063    }