more accurate determination of annotation modification
[jalview.git] / src / jalview / io / AnnotationFile.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)\r
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle\r
4  * \r
5  * This file is part of Jalview.\r
6  * \r
7  * Jalview is free software: you can redistribute it and/or\r
8  * modify it under the terms of the GNU General Public License \r
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\r
10  * \r
11  * Jalview is distributed in the hope that it will be useful, but \r
12  * WITHOUT ANY WARRANTY; without even the implied warranty \r
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR \r
14  * PURPOSE.  See the GNU General Public License for more details.\r
15  * \r
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.\r
17  */\r
18 package jalview.io;\r
19 \r
20 import java.io.*;\r
21 import java.net.*;\r
22 import java.util.*;\r
23 \r
24 import jalview.analysis.*;\r
25 import jalview.datamodel.*;\r
26 import jalview.schemes.*;\r
27 \r
28 public class AnnotationFile\r
29 {\r
30   StringBuffer text = new StringBuffer("JALVIEW_ANNOTATION\n"\r
31           + "# Created: " + new java.util.Date() + "\n\n");\r
32 \r
33   /**\r
34    * convenience method for pre-2.4 feature files which have no view, hidden\r
35    * columns or hidden row keywords.\r
36    * \r
37    * @param annotations\r
38    * @param groups\r
39    * @param properties\r
40    * @return feature file as a string.\r
41    */\r
42   public String printAnnotations(AlignmentAnnotation[] annotations,\r
43           Vector groups, Hashtable properties)\r
44   {\r
45     return printAnnotations(annotations, groups, properties, null);\r
46 \r
47   }\r
48 \r
49   /**\r
50    * hold all the information about a particular view definition read from or\r
51    * written out in an annotations file.\r
52    */\r
53   public class ViewDef\r
54   {\r
55     public String viewname;\r
56 \r
57     public HiddenSequences hidseqs;\r
58 \r
59     public ColumnSelection hiddencols;\r
60 \r
61     public Vector visibleGroups;\r
62 \r
63     public Hashtable hiddenRepSeqs;\r
64 \r
65     public ViewDef(String viewname, HiddenSequences hidseqs,\r
66             ColumnSelection hiddencols, Hashtable hiddenRepSeqs)\r
67     {\r
68       this.viewname = viewname;\r
69       this.hidseqs = hidseqs;\r
70       this.hiddencols = hiddencols;\r
71       this.hiddenRepSeqs = hiddenRepSeqs;\r
72     }\r
73   }\r
74 \r
75   /**\r
76    * Prepare an annotation file given a set of annotations, groups, alignment\r
77    * properties and views.\r
78    * \r
79    * @param annotations\r
80    * @param groups\r
81    * @param properties\r
82    * @param views\r
83    * @return annotation file\r
84    */\r
85   public String printAnnotations(AlignmentAnnotation[] annotations,\r
86           Vector groups, Hashtable properties, ViewDef[] views)\r
87   {\r
88     // TODO: resolve views issue : annotationFile could contain visible region,\r
89     // or full data + hidden region specifications for a view.\r
90     if (annotations != null)\r
91     {\r
92       boolean oneColour = true;\r
93       AlignmentAnnotation row;\r
94       String comma;\r
95       SequenceI refSeq = null;\r
96       SequenceGroup refGroup = null;\r
97 \r
98       StringBuffer colours = new StringBuffer();\r
99       StringBuffer graphLine = new StringBuffer();\r
100       StringBuffer rowprops = new StringBuffer();\r
101       Hashtable graphGroup = new Hashtable();\r
102 \r
103       java.awt.Color color;\r
104 \r
105       for (int i = 0; i < annotations.length; i++)\r
106       {\r
107         row = annotations[i];\r
108 \r
109         if (!row.visible && !row.hasScore())\r
110         {\r
111           continue;\r
112         }\r
113 \r
114         color = null;\r
115         oneColour = true;\r
116 \r
117         if (row.sequenceRef == null)\r
118         {\r
119           if (refSeq != null)\r
120           {\r
121             text.append("\nSEQUENCE_REF\tALIGNMENT\n");\r
122           }\r
123 \r
124           refSeq = null;\r
125         }\r
126 \r
127         else\r
128         {\r
129           if (refSeq == null || refSeq != row.sequenceRef)\r
130           {\r
131             refSeq = row.sequenceRef;\r
132             text.append("\nSEQUENCE_REF\t" + refSeq.getName() + "\n");\r
133           }\r
134         }\r
135         // mark any group references for the row\r
136         if (row.groupRef == null)\r
137         {\r
138 \r
139           if (refGroup != null)\r
140           {\r
141             text.append("\nGROUP_REF\tALIGNMENT\n");\r
142           }\r
143 \r
144           refGroup = null;\r
145         }\r
146         else\r
147         {\r
148           if (refGroup == null || refGroup != row.groupRef)\r
149           {\r
150             refGroup = row.groupRef;\r
151             text.append("\nGROUP_REF\t" + refGroup.getName() + "\n");\r
152           }\r
153         }\r
154 \r
155         boolean hasGlyphs = row.hasIcons, hasLabels = row.hasText, hasValues = row.hasScore, hasText = false;\r
156         // lookahead to check what the annotation row object actually contains.\r
157         for (int j = 0; row.annotations != null\r
158                 && j < row.annotations.length\r
159                 && (!hasGlyphs || !hasLabels || !hasValues); j++)\r
160         {\r
161           if (row.annotations[j] != null)\r
162           {\r
163             hasLabels |= (row.annotations[j].displayCharacter != null\r
164                     && row.annotations[j].displayCharacter.length() > 0 && !row.annotations[j].displayCharacter\r
165                     .equals(" "));\r
166             hasGlyphs |= (row.annotations[j].secondaryStructure != 0 && row.annotations[j].secondaryStructure != ' ');\r
167             hasValues |= (row.annotations[j].value != Float.NaN); // NaNs can't\r
168             // be\r
169             // rendered..\r
170             hasText |= (row.annotations[j].description != null && row.annotations[j].description\r
171                     .length() > 0);\r
172           }\r
173         }\r
174 \r
175         if (row.graph == AlignmentAnnotation.NO_GRAPH)\r
176         {\r
177           text.append("NO_GRAPH\t");\r
178           hasValues = false; // only secondary structure\r
179           // hasLabels = false; // and annotation description string.\r
180         }\r
181         else\r
182         {\r
183           if (row.graph == AlignmentAnnotation.BAR_GRAPH)\r
184           {\r
185             text.append("BAR_GRAPH\t");\r
186             hasGlyphs = false; // no secondary structure\r
187 \r
188           }\r
189           else if (row.graph == AlignmentAnnotation.LINE_GRAPH)\r
190           {\r
191             hasGlyphs = false; // no secondary structure\r
192             text.append("LINE_GRAPH\t");\r
193           }\r
194 \r
195           if (row.getThreshold() != null)\r
196           {\r
197             graphLine\r
198                     .append("GRAPHLINE\t"\r
199                             + row.label\r
200                             + "\t"\r
201                             + row.getThreshold().value\r
202                             + "\t"\r
203                             + row.getThreshold().label\r
204                             + "\t"\r
205                             + jalview.util.Format.getHexString(row\r
206                                     .getThreshold().colour) + "\n");\r
207           }\r
208 \r
209           if (row.graphGroup > -1)\r
210           {\r
211             String key = String.valueOf(row.graphGroup);\r
212             if (graphGroup.containsKey(key))\r
213             {\r
214               graphGroup.put(key, graphGroup.get(key) + "\t" + row.label);\r
215             }\r
216             else\r
217             {\r
218               graphGroup.put(key, row.label);\r
219             }\r
220           }\r
221         }\r
222 \r
223         text.append(row.label + "\t");\r
224         if (row.description != null)\r
225         {\r
226           text.append(row.description + "\t");\r
227         }\r
228         for (int j = 0; row.annotations != null\r
229                 && j < row.annotations.length; j++)\r
230         {\r
231           if (refSeq != null\r
232                   && jalview.util.Comparison.isGap(refSeq.getCharAt(j)))\r
233           {\r
234             continue;\r
235           }\r
236 \r
237           if (row.annotations[j] != null)\r
238           {\r
239             comma = "";\r
240             if (hasGlyphs) // could be also hasGlyphs || ...\r
241             {\r
242 \r
243               text.append(comma);\r
244               if (row.annotations[j].secondaryStructure != ' ')\r
245               {\r
246                 // only write out the field if its not whitespace.\r
247                 text.append(row.annotations[j].secondaryStructure);\r
248               }\r
249               comma = ",";\r
250             }\r
251             if (hasValues)\r
252             {\r
253               if (row.annotations[j].value != Float.NaN)\r
254               {\r
255                 text.append(comma + row.annotations[j].value);\r
256               }\r
257               else\r
258               {\r
259                 System.err.println("Skipping NaN - not valid value.");\r
260                 text.append(comma + 0f);// row.annotations[j].value);\r
261               }\r
262               comma = ",";\r
263             }\r
264             if (hasLabels)\r
265             {\r
266               // TODO: labels are emitted after values for bar graphs.\r
267               if // empty labels are allowed, so\r
268               (row.annotations[j].displayCharacter != null\r
269                       && row.annotations[j].displayCharacter.length() > 0\r
270                       && !row.annotations[j].displayCharacter.equals(" "))\r
271               {\r
272                 text.append(comma + row.annotations[j].displayCharacter);\r
273                 comma = ",";\r
274               }\r
275             }\r
276             if (hasText)\r
277             {\r
278               if (row.annotations[j].description != null\r
279                       && row.annotations[j].description.length() > 0\r
280                       && !row.annotations[j].description\r
281                               .equals(row.annotations[j].displayCharacter))\r
282               {\r
283                 text.append(comma + row.annotations[j].description);\r
284                 comma = ",";\r
285               }\r
286             }\r
287             if (color != null && !color.equals(row.annotations[j].colour))\r
288             {\r
289               oneColour = false;\r
290             }\r
291 \r
292             color = row.annotations[j].colour;\r
293 \r
294             if (row.annotations[j].colour != null\r
295                     && row.annotations[j].colour != java.awt.Color.black)\r
296             {\r
297               text.append(comma\r
298                       + "["\r
299                       + jalview.util.Format\r
300                               .getHexString(row.annotations[j].colour)\r
301                       + "]");\r
302               comma = ",";\r
303             }\r
304           }\r
305           text.append("|");\r
306         }\r
307 \r
308         if (row.hasScore())\r
309           text.append("\t" + row.score);\r
310 \r
311         text.append("\n");\r
312 \r
313         if (color != null && color != java.awt.Color.black && oneColour)\r
314         {\r
315           colours.append("COLOUR\t" + row.label + "\t"\r
316                   + jalview.util.Format.getHexString(color) + "\n");\r
317         }\r
318         if (row.scaleColLabel || row.showAllColLabels\r
319                 || row.centreColLabels)\r
320         {\r
321           rowprops.append("ROWPROPERTIES\t" + row.label);\r
322           rowprops.append("\tscaletofit=" + row.scaleColLabel);\r
323           rowprops.append("\tshowalllabs=" + row.showAllColLabels);\r
324           rowprops.append("\tcentrelabs=" + row.centreColLabels);\r
325           rowprops.append("\n");\r
326         }\r
327       }\r
328 \r
329       text.append("\n");\r
330 \r
331       text.append(colours.toString());\r
332       text.append(graphLine.toString());\r
333       if (graphGroup.size() > 0)\r
334       {\r
335         text.append("COMBINE\t");\r
336         Enumeration en = graphGroup.elements();\r
337         while (en.hasMoreElements())\r
338         {\r
339           text.append(en.nextElement() + "\n");\r
340         }\r
341       }\r
342       text.append(rowprops.toString());\r
343     }\r
344 \r
345     if (groups != null)\r
346     {\r
347       printGroups(groups);\r
348     }\r
349 \r
350     if (properties != null)\r
351     {\r
352       text.append("\n\nALIGNMENT");\r
353       Enumeration en = properties.keys();\r
354       while (en.hasMoreElements())\r
355       {\r
356         String key = en.nextElement().toString();\r
357         text.append("\t" + key + "=" + properties.get(key));\r
358       }\r
359       // TODO: output alignment visualization settings here if required\r
360 \r
361     }\r
362 \r
363     return text.toString();\r
364   }\r
365 \r
366   public void printGroups(Vector sequenceGroups)\r
367   {\r
368     SequenceGroup sg;\r
369     SequenceI seqrep = null;\r
370     for (int i = 0; i < sequenceGroups.size(); i++)\r
371     {\r
372       sg = (SequenceGroup) sequenceGroups.elementAt(i);\r
373       if (!sg.hasSeqrep())\r
374       {\r
375         text.append("SEQUENCE_GROUP\t" + sg.getName() + "\t"\r
376                 + (sg.getStartRes() + 1) + "\t" + (sg.getEndRes() + 1)\r
377                 + "\t" + "-1\t");\r
378         seqrep = null;\r
379       }\r
380       else\r
381       {\r
382         seqrep = sg.getSeqrep();\r
383         text.append("SEQUENCE_REF\t" + seqrep.getName() + "\n");\r
384         text.append("SEQUENCE_GROUP\t" + sg.getName() + "\t"\r
385                 + (seqrep.findPosition(sg.getStartRes())) + "\t"\r
386                 + (seqrep.findPosition(sg.getEndRes())) + "\t" + "-1\t");\r
387       }\r
388       for (int s = 0; s < sg.getSize(); s++)\r
389       {\r
390         text.append(sg.getSequenceAt(s).getName() + "\t");\r
391       }\r
392 \r
393       text.append("\nPROPERTIES\t" + sg.getName() + "\t");\r
394 \r
395       if (sg.getDescription() != null)\r
396       {\r
397         text.append("description=" + sg.getDescription() + "\t");\r
398       }\r
399       if (sg.cs != null)\r
400       {\r
401         text.append("colour=" + ColourSchemeProperty.getColourName(sg.cs)\r
402                 + "\t");\r
403         if (sg.cs.getThreshold() != 0)\r
404         {\r
405           text.append("pidThreshold=" + sg.cs.getThreshold());\r
406         }\r
407         if (sg.cs.conservationApplied())\r
408         {\r
409           text.append("consThreshold=" + sg.cs.getConservationInc() + "\t");\r
410         }\r
411       }\r
412       text.append("outlineColour="\r
413               + jalview.util.Format.getHexString(sg.getOutlineColour())\r
414               + "\t");\r
415 \r
416       text.append("displayBoxes=" + sg.getDisplayBoxes() + "\t");\r
417       text.append("displayText=" + sg.getDisplayText() + "\t");\r
418       text.append("colourText=" + sg.getColourText() + "\t");\r
419       text.append("showUnconserved=" + sg.getShowNonconserved() + "\t");\r
420       if (sg.textColour != java.awt.Color.black)\r
421       {\r
422         text.append("textCol1="\r
423                 + jalview.util.Format.getHexString(sg.textColour) + "\t");\r
424       }\r
425       if (sg.textColour2 != java.awt.Color.white)\r
426       {\r
427         text.append("textCol2="\r
428                 + jalview.util.Format.getHexString(sg.textColour2) + "\t");\r
429       }\r
430       if (sg.thresholdTextColour != 0)\r
431       {\r
432         text.append("textColThreshold=" + sg.thresholdTextColour + "\t");\r
433       }\r
434       if (sg.idColour != null)\r
435       {\r
436         text.append("idColour="\r
437                 + jalview.util.Format.getHexString(sg.idColour) + "\t");\r
438       }\r
439       if (sg.isHidereps())\r
440       {\r
441         text.append("hide=true\t");\r
442       }\r
443       if (sg.isHideCols())\r
444       {\r
445         text.append("hidecols=true\t");\r
446       }\r
447       if (seqrep != null)\r
448       {\r
449         // terminate the last line and clear the sequence ref for the group\r
450         text.append("\nSEQUENCE_REF");\r
451       }\r
452       text.append("\n\n");\r
453 \r
454     }\r
455   }\r
456 \r
457   SequenceI refSeq = null;\r
458 \r
459   String refSeqId = null;\r
460 \r
461   public boolean readAnnotationFile(AlignmentI al, String file,\r
462           String protocol)\r
463   {\r
464     BufferedReader in = null;\r
465     try\r
466     {\r
467       if (protocol.equals(AppletFormatAdapter.FILE))\r
468       {\r
469         in = new BufferedReader(new FileReader(file));\r
470       }\r
471       else if (protocol.equals(AppletFormatAdapter.URL))\r
472       {\r
473         URL url = new URL(file);\r
474         in = new BufferedReader(new InputStreamReader(url.openStream()));\r
475       }\r
476       else if (protocol.equals(AppletFormatAdapter.PASTE))\r
477       {\r
478         in = new BufferedReader(new StringReader(file));\r
479       }\r
480       else if (protocol.equals(AppletFormatAdapter.CLASSLOADER))\r
481       {\r
482         java.io.InputStream is = getClass().getResourceAsStream("/" + file);\r
483         if (is != null)\r
484         {\r
485           in = new BufferedReader(new java.io.InputStreamReader(is));\r
486         }\r
487       }\r
488       if (in != null)\r
489       {\r
490         return parseAnnotationFrom(al, in);\r
491       }\r
492 \r
493     } catch (Exception ex)\r
494     {\r
495       ex.printStackTrace();\r
496       System.out.println("Problem reading annotation file: " + ex);\r
497       return false;\r
498     }\r
499     return false;\r
500   }\r
501 \r
502   public boolean parseAnnotationFrom(AlignmentI al, BufferedReader in)\r
503           throws Exception\r
504   {\r
505     boolean modified=false;\r
506     String groupRef = null;\r
507     Hashtable groupRefRows = new Hashtable();\r
508 \r
509     Hashtable autoAnnots = new Hashtable();\r
510     {\r
511       String line, label, description, token;\r
512       int graphStyle, index;\r
513       int refSeqIndex = 1;\r
514       int existingAnnotations = 0;\r
515       // when true - will add new rows regardless of whether they are duplicate\r
516       // auto-annotation like consensus or conservation graphs\r
517       boolean overrideAutoAnnot = false;\r
518       if (al.getAlignmentAnnotation() != null)\r
519       {\r
520         existingAnnotations = al.getAlignmentAnnotation().length;\r
521         if (existingAnnotations > 0)\r
522         {\r
523           AlignmentAnnotation[] aa = al.getAlignmentAnnotation();\r
524           for (int aai = 0; aai < aa.length; aai++)\r
525           {\r
526             if (aa[aai].autoCalculated)\r
527             {\r
528               // make a note of the name and description\r
529               autoAnnots\r
530                       .put(autoAnnotsKey(aa[aai], aa[aai].sequenceRef, (aa[aai].groupRef==null ? null : aa[aai].groupRef.getName())\r
531                               ),\r
532                               new Integer(1));\r
533             }\r
534           }\r
535         }\r
536       }\r
537 \r
538       int alWidth = al.getWidth();\r
539 \r
540       StringTokenizer st;\r
541       Annotation[] annotations;\r
542       AlignmentAnnotation annotation = null;\r
543 \r
544       // First confirm this is an Annotation file\r
545       boolean jvAnnotationFile = false;\r
546       while ((line = in.readLine()) != null)\r
547       {\r
548         if (line.indexOf("#") == 0)\r
549         {\r
550           continue;\r
551         }\r
552 \r
553         if (line.indexOf("JALVIEW_ANNOTATION") > -1)\r
554         {\r
555           jvAnnotationFile = true;\r
556           break;\r
557         }\r
558       }\r
559 \r
560       if (!jvAnnotationFile)\r
561       {\r
562         in.close();\r
563         return false;\r
564       }\r
565 \r
566       while ((line = in.readLine()) != null)\r
567       {\r
568         if (line.indexOf("#") == 0\r
569                 || line.indexOf("JALVIEW_ANNOTATION") > -1\r
570                 || line.length() == 0)\r
571         {\r
572           continue;\r
573         }\r
574 \r
575         st = new StringTokenizer(line, "\t");\r
576         token = st.nextToken();\r
577         if (token.equalsIgnoreCase("COLOUR"))\r
578         {\r
579           // TODO: use graduated colour def'n here too\r
580           colourAnnotations(al, st.nextToken(), st.nextToken());\r
581           modified=true;continue;\r
582         }\r
583 \r
584         else if (token.equalsIgnoreCase("COMBINE"))\r
585         {\r
586           combineAnnotations(al, st);\r
587           modified=true;continue;\r
588         }\r
589         else if (token.equalsIgnoreCase("ROWPROPERTIES"))\r
590         {\r
591           addRowProperties(al, st);\r
592           modified=true;continue;\r
593         }\r
594         else if (token.equalsIgnoreCase("GRAPHLINE"))\r
595         {\r
596           addLine(al, st);\r
597           modified=true;\r
598           continue;\r
599         }\r
600 \r
601         else if (token.equalsIgnoreCase("SEQUENCE_REF"))\r
602         {\r
603           if (st.hasMoreTokens())\r
604           {\r
605             refSeq = al.findName(refSeqId = st.nextToken());\r
606             if (refSeq == null)\r
607             {\r
608               refSeqId = null;\r
609             }\r
610             try\r
611             {\r
612               refSeqIndex = Integer.parseInt(st.nextToken());\r
613               if (refSeqIndex < 1)\r
614               {\r
615                 refSeqIndex = 1;\r
616                 System.out\r
617                         .println("WARNING: SEQUENCE_REF index must be > 0 in AnnotationFile");\r
618               }\r
619             } catch (Exception ex)\r
620             {\r
621               refSeqIndex = 1;\r
622             }\r
623           }\r
624           else\r
625           {\r
626             refSeq = null;\r
627             refSeqId = null;\r
628           }\r
629           continue;\r
630         }\r
631         else if (token.equalsIgnoreCase("GROUP_REF"))\r
632         {\r
633           // Group references could be forward or backwards, so they are\r
634           // resolved after the whole file is read in\r
635           groupRef = null;\r
636           if (st.hasMoreTokens())\r
637           {\r
638             groupRef = st.nextToken();\r
639             if (groupRef.length() < 1)\r
640             {\r
641               groupRef = null; // empty string\r
642             }\r
643             else\r
644             {\r
645               if (groupRefRows.get(groupRef) == null)\r
646               {\r
647                 groupRefRows.put(groupRef, new Vector());\r
648               }\r
649             }\r
650           }\r
651           continue;\r
652         }\r
653         else if (token.equalsIgnoreCase("SEQUENCE_GROUP"))\r
654         {\r
655           addGroup(al, st);\r
656           continue;\r
657         }\r
658 \r
659         else if (token.equalsIgnoreCase("PROPERTIES"))\r
660         {\r
661           addProperties(al, st);\r
662           modified=true;          continue;\r
663         }\r
664 \r
665         else if (token.equalsIgnoreCase("BELOW_ALIGNMENT"))\r
666         {\r
667           setBelowAlignment(al, st);\r
668           modified=true;continue;\r
669         }\r
670         else if (token.equalsIgnoreCase("ALIGNMENT"))\r
671         {\r
672           addAlignmentDetails(al, st);\r
673           modified=true;continue;\r
674         }\r
675 \r
676         // Parse out the annotation row\r
677         graphStyle = AlignmentAnnotation.getGraphValueFromString(token);\r
678         label = st.nextToken();\r
679 \r
680         index = 0;\r
681         annotations = new Annotation[alWidth];\r
682         description = null;\r
683         float score = Float.NaN;\r
684 \r
685         if (st.hasMoreTokens())\r
686         {\r
687           line = st.nextToken();\r
688 \r
689           if (line.indexOf("|") == -1)\r
690           {\r
691             description = line;\r
692             if (st.hasMoreTokens())\r
693               line = st.nextToken();\r
694           }\r
695 \r
696           if (st.hasMoreTokens())\r
697           {\r
698             // This must be the score\r
699             score = Float.valueOf(st.nextToken()).floatValue();\r
700           }\r
701 \r
702           st = new StringTokenizer(line, "|", true);\r
703 \r
704           boolean emptyColumn = true;\r
705           boolean onlyOneElement = (st.countTokens() == 1);\r
706 \r
707           while (st.hasMoreElements() && index < alWidth)\r
708           {\r
709             token = st.nextToken().trim();\r
710 \r
711             if (onlyOneElement)\r
712             {\r
713               try\r
714               {\r
715                 score = Float.valueOf(token).floatValue();\r
716                 break;\r
717               } catch (NumberFormatException ex)\r
718               {\r
719               }\r
720             }\r
721 \r
722             if (token.equals("|"))\r
723             {\r
724               if (emptyColumn)\r
725               {\r
726                 index++;\r
727               }\r
728 \r
729               emptyColumn = true;\r
730             }\r
731             else\r
732             {\r
733               annotations[index++] = parseAnnotation(token, graphStyle);\r
734               emptyColumn = false;\r
735             }\r
736           }\r
737 \r
738         }\r
739 \r
740         annotation = new AlignmentAnnotation(label, description,\r
741                 (index == 0) ? null : annotations, 0, 0, graphStyle);\r
742 \r
743         annotation.score = score;\r
744         if (!overrideAutoAnnot\r
745                 && autoAnnots\r
746                         .containsKey(autoAnnotsKey(annotation, refSeq, groupRef)))\r
747         {\r
748           // skip - we've already got an automatic annotation of this type.\r
749           continue;\r
750         }\r
751         // otherwise add it!\r
752         if (refSeq != null)\r
753         {\r
754 \r
755           annotation.belowAlignment = false;\r
756           // make a copy of refSeq so we can find other matches in the alignment\r
757           SequenceI referedSeq = refSeq;\r
758           do\r
759           {\r
760             // copy before we do any mapping business.\r
761             // TODO: verify that undo/redo with 1:many sequence associated\r
762             // annotations can be undone correctly\r
763             AlignmentAnnotation ann = new AlignmentAnnotation(annotation);\r
764             annotation\r
765                     .createSequenceMapping(referedSeq, refSeqIndex, false);\r
766             annotation.adjustForAlignment();\r
767             referedSeq.addAlignmentAnnotation(annotation);\r
768             al.addAnnotation(annotation);\r
769             al.setAnnotationIndex(annotation,\r
770                     al.getAlignmentAnnotation().length\r
771                             - existingAnnotations - 1);\r
772             if (groupRef != null)\r
773             {\r
774               ((Vector) groupRefRows.get(groupRef)).addElement(annotation);\r
775             }\r
776             // and recover our virgin copy to use again if necessary.\r
777             annotation = ann;\r
778 \r
779           } while (refSeqId != null\r
780                   && (referedSeq = al.findName(referedSeq, refSeqId, true)) != null);\r
781         }\r
782         else\r
783         {\r
784           al.addAnnotation(annotation);\r
785           al.setAnnotationIndex(annotation,\r
786                   al.getAlignmentAnnotation().length - existingAnnotations\r
787                           - 1);\r
788           if (groupRef != null)\r
789           {\r
790             ((Vector) groupRefRows.get(groupRef)).addElement(annotation);\r
791           }\r
792         }\r
793         // and set modification flag\r
794         modified=true;\r
795       }\r
796       // Finally, resolve the groupRefs\r
797       Enumeration en = groupRefRows.keys();\r
798       SequenceGroup theGroup = null;\r
799 \r
800       while (en.hasMoreElements())\r
801       {\r
802         groupRef = (String) en.nextElement();\r
803         boolean matched = false;\r
804         // Resolve group: TODO: add a getGroupByName method to alignments\r
805         Vector grps = al.getGroups();\r
806         for (int g = 0, gSize = grps.size(); g < gSize; g++)\r
807         {\r
808           theGroup = (SequenceGroup) grps.elementAt(g);\r
809           if (theGroup.getName().equals(groupRef))\r
810           {\r
811             if (matched)\r
812             {\r
813               // TODO: specify and implement duplication of alignment annotation\r
814               // for multiple group references.\r
815               System.err\r
816                       .println("Ignoring 1:many group reference mappings for group name '"\r
817                               + groupRef + "'");\r
818             }\r
819             else\r
820             {\r
821               matched = true;\r
822               Vector rowset = (Vector) groupRefRows.get(groupRef);\r
823               if (rowset != null && rowset.size() > 0)\r
824               {\r
825                 AlignmentAnnotation alan = null;\r
826                 for (int elm = 0, elmSize = rowset.size(); elm < elmSize; elm++)\r
827                 {\r
828                   alan = (AlignmentAnnotation) rowset.elementAt(elm);\r
829                   alan.groupRef = theGroup;\r
830                 }\r
831               }\r
832             }\r
833           }\r
834         }\r
835         ((Vector) groupRefRows.get(groupRef)).removeAllElements();\r
836       }\r
837     }\r
838     return modified;\r
839   }\r
840 \r
841   private Object autoAnnotsKey(AlignmentAnnotation annotation, SequenceI refSeq, String groupRef)\r
842   {\r
843     return annotation.graph\r
844     + "\t"\r
845     + annotation.label\r
846     + "\t"\r
847     + annotation.description\r
848     + "\t"\r
849     + (refSeq != null ? refSeq\r
850             .getDisplayId(true) : "");\r
851   }\r
852 \r
853   Annotation parseAnnotation(String string, int graphStyle)\r
854   {\r
855     boolean hasSymbols = (graphStyle == AlignmentAnnotation.NO_GRAPH); // don't\r
856     // do the\r
857     // glyph\r
858     // test\r
859     // if we\r
860     // don't\r
861     // want\r
862     // secondary\r
863     // structure\r
864     String desc = null, displayChar = null;\r
865     char ss = ' '; // secondaryStructure\r
866     float value = 0;\r
867     boolean parsedValue = false, dcset = false;\r
868 \r
869     // find colour here\r
870     java.awt.Color colour = null;\r
871     int i = string.indexOf("[");\r
872     int j = string.indexOf("]");\r
873     if (i > -1 && j > -1)\r
874     {\r
875       UserColourScheme ucs = new UserColourScheme();\r
876 \r
877       colour = ucs.getColourFromString(string.substring(i + 1, j));\r
878       if (i > 0 && string.charAt(i - 1) == ',')\r
879       {\r
880         // clip the preceding comma as well\r
881         i--;\r
882       }\r
883       string = string.substring(0, i) + string.substring(j + 1);\r
884     }\r
885 \r
886     StringTokenizer st = new StringTokenizer(string, ",", true);\r
887     String token;\r
888     boolean seenContent = false;\r
889     int pass = 0;\r
890     while (st.hasMoreTokens())\r
891     {\r
892       pass++;\r
893       token = st.nextToken().trim();\r
894       if (token.equals(","))\r
895       {\r
896         if (!seenContent && parsedValue && !dcset)\r
897         {\r
898           // allow the value below the bar/line to be empty\r
899           dcset = true;\r
900           displayChar = " ";\r
901         }\r
902         seenContent = false;\r
903         continue;\r
904       }\r
905       else\r
906       {\r
907         seenContent = true;\r
908       }\r
909 \r
910       if (!parsedValue)\r
911       {\r
912         try\r
913         {\r
914           displayChar = token;\r
915           // foo\r
916           value = new Float(token).floatValue();\r
917           parsedValue = true;\r
918           continue;\r
919         } catch (NumberFormatException ex)\r
920         {\r
921         }\r
922       }\r
923       else\r
924       {\r
925         if (token.length() == 1)\r
926         {\r
927           displayChar = token;\r
928         }\r
929       }\r
930       if (hasSymbols\r
931               && (token.equals("H") || token.equals("E") || token\r
932                       .equals(" ")))\r
933       {\r
934         // Either this character represents a helix or sheet\r
935         // or an integer which can be displayed\r
936         ss = token.charAt(0);\r
937         if (displayChar.equals(token.substring(0, 1)))\r
938         {\r
939           displayChar = "";\r
940         }\r
941       }\r
942       else if (desc == null || (parsedValue && pass > 2))\r
943       {\r
944         desc = token;\r
945       }\r
946 \r
947     }\r
948     // if (!dcset && string.charAt(string.length() - 1) == ',')\r
949     // {\r
950     // displayChar = " "; // empty display char symbol.\r
951     // }\r
952     if (displayChar != null && desc != null && desc.length() == 1)\r
953     {\r
954       if (displayChar.length() > 1)\r
955       {\r
956         // switch desc and displayChar - legacy support\r
957         String tmp = displayChar;\r
958         displayChar = desc;\r
959         desc = tmp;\r
960       }\r
961       else\r
962       {\r
963         if (displayChar.equals(desc))\r
964         {\r
965           // duplicate label - hangover from the 'robust parser' above\r
966           desc = null;\r
967         }\r
968       }\r
969     }\r
970     Annotation anot = new Annotation(displayChar, desc, ss, value);\r
971 \r
972     anot.colour = colour;\r
973 \r
974     return anot;\r
975   }\r
976 \r
977   void colourAnnotations(AlignmentI al, String label, String colour)\r
978   {\r
979     UserColourScheme ucs = new UserColourScheme(colour);\r
980     Annotation[] annotations;\r
981     for (int i = 0; i < al.getAlignmentAnnotation().length; i++)\r
982     {\r
983       if (al.getAlignmentAnnotation()[i].label.equalsIgnoreCase(label))\r
984       {\r
985         annotations = al.getAlignmentAnnotation()[i].annotations;\r
986         for (int j = 0; j < annotations.length; j++)\r
987         {\r
988           if (annotations[j] != null)\r
989           {\r
990             annotations[j].colour = ucs.findColour('A');\r
991           }\r
992         }\r
993       }\r
994     }\r
995   }\r
996 \r
997   void combineAnnotations(AlignmentI al, StringTokenizer st)\r
998   {\r
999     int graphGroup = -1;\r
1000     String group = st.nextToken();\r
1001     // First make sure we are not overwriting the graphIndex\r
1002     if (al.getAlignmentAnnotation() != null)\r
1003     {\r
1004       for (int i = 0; i < al.getAlignmentAnnotation().length; i++)\r
1005       {\r
1006         if (al.getAlignmentAnnotation()[i].label.equalsIgnoreCase(group))\r
1007         {\r
1008           graphGroup = al.getAlignmentAnnotation()[i].graphGroup + 1;\r
1009           al.getAlignmentAnnotation()[i].graphGroup = graphGroup;\r
1010           break;\r
1011         }\r
1012       }\r
1013 \r
1014       // Now update groups\r
1015       while (st.hasMoreTokens())\r
1016       {\r
1017         group = st.nextToken();\r
1018         for (int i = 0; i < al.getAlignmentAnnotation().length; i++)\r
1019         {\r
1020           if (al.getAlignmentAnnotation()[i].label.equalsIgnoreCase(group))\r
1021           {\r
1022             al.getAlignmentAnnotation()[i].graphGroup = graphGroup;\r
1023             break;\r
1024           }\r
1025         }\r
1026       }\r
1027     }\r
1028     else\r
1029     {\r
1030       System.err\r
1031               .println("Couldn't combine annotations. None are added to alignment yet!");\r
1032     }\r
1033   }\r
1034 \r
1035   void addLine(AlignmentI al, StringTokenizer st)\r
1036   {\r
1037     String group = st.nextToken();\r
1038     AlignmentAnnotation annotation = null, alannot[] = al\r
1039             .getAlignmentAnnotation();\r
1040     if (alannot != null)\r
1041     {\r
1042       for (int i = 0; i < alannot.length; i++)\r
1043       {\r
1044         if (alannot[i].label.equalsIgnoreCase(group))\r
1045         {\r
1046           annotation = alannot[i];\r
1047           break;\r
1048         }\r
1049       }\r
1050     }\r
1051     if (annotation == null)\r
1052     {\r
1053       return;\r
1054     }\r
1055     float value = new Float(st.nextToken()).floatValue();\r
1056     String label = st.hasMoreTokens() ? st.nextToken() : null;\r
1057     java.awt.Color colour = null;\r
1058     if (st.hasMoreTokens())\r
1059     {\r
1060       UserColourScheme ucs = new UserColourScheme(st.nextToken());\r
1061       colour = ucs.findColour('A');\r
1062     }\r
1063 \r
1064     annotation.setThreshold(new GraphLine(value, label, colour));\r
1065   }\r
1066 \r
1067   void addGroup(AlignmentI al, StringTokenizer st)\r
1068   {\r
1069     SequenceGroup sg = new SequenceGroup();\r
1070     sg.setName(st.nextToken());\r
1071     String rng = "";\r
1072     try\r
1073     {\r
1074       rng = st.nextToken();\r
1075       if (rng.length() > 0 && !rng.startsWith("*"))\r
1076       {\r
1077         sg.setStartRes(Integer.parseInt(rng) - 1);\r
1078       }\r
1079       else\r
1080       {\r
1081         sg.setStartRes(0);\r
1082       }\r
1083       rng = st.nextToken();\r
1084       if (rng.length() > 0 && !rng.startsWith("*"))\r
1085       {\r
1086         sg.setEndRes(Integer.parseInt(rng) - 1);\r
1087       }\r
1088       else\r
1089       {\r
1090         sg.setEndRes(al.getWidth() - 1);\r
1091       }\r
1092     } catch (Exception e)\r
1093     {\r
1094       System.err\r
1095               .println("Couldn't parse Group Start or End Field as '*' or a valid column or sequence index: '"\r
1096                       + rng + "' - assuming alignment width for group.");\r
1097       // assume group is full width\r
1098       sg.setStartRes(0);\r
1099       sg.setEndRes(al.getWidth() - 1);\r
1100     }\r
1101 \r
1102     String index = st.nextToken();\r
1103     if (index.equals("-1"))\r
1104     {\r
1105       while (st.hasMoreElements())\r
1106       {\r
1107         sg.addSequence(al.findName(st.nextToken()), false);\r
1108       }\r
1109     }\r
1110     else\r
1111     {\r
1112       StringTokenizer st2 = new StringTokenizer(index, ",");\r
1113 \r
1114       while (st2.hasMoreTokens())\r
1115       {\r
1116         String tmp = st2.nextToken();\r
1117         if (tmp.equals("*"))\r
1118         {\r
1119           for (int i = 0; i < al.getHeight(); i++)\r
1120           {\r
1121             sg.addSequence(al.getSequenceAt(i), false);\r
1122           }\r
1123         }\r
1124         else if (tmp.indexOf("-") >= 0)\r
1125         {\r
1126           StringTokenizer st3 = new StringTokenizer(tmp, "-");\r
1127 \r
1128           int start = (Integer.parseInt(st3.nextToken()));\r
1129           int end = (Integer.parseInt(st3.nextToken()));\r
1130 \r
1131           if (end > start)\r
1132           {\r
1133             for (int i = start; i <= end; i++)\r
1134             {\r
1135               sg.addSequence(al.getSequenceAt(i - 1), false);\r
1136             }\r
1137           }\r
1138         }\r
1139         else\r
1140         {\r
1141           sg.addSequence(al.getSequenceAt(Integer.parseInt(tmp) - 1), false);\r
1142         }\r
1143       }\r
1144     }\r
1145 \r
1146     if (refSeq != null)\r
1147     {\r
1148       sg.setStartRes(refSeq.findIndex(sg.getStartRes() + 1) - 1);\r
1149       sg.setEndRes(refSeq.findIndex(sg.getEndRes() + 1) - 1);\r
1150       sg.setSeqrep(refSeq);\r
1151     }\r
1152 \r
1153     if (sg.getSize() > 0)\r
1154     {\r
1155       al.addGroup(sg);\r
1156     }\r
1157   }\r
1158 \r
1159   void addRowProperties(AlignmentI al, StringTokenizer st)\r
1160   {\r
1161     String label = st.nextToken(), keyValue, key, value;\r
1162     boolean scaletofit = false, centerlab = false, showalllabs = false;\r
1163     while (st.hasMoreTokens())\r
1164     {\r
1165       keyValue = st.nextToken();\r
1166       key = keyValue.substring(0, keyValue.indexOf("="));\r
1167       value = keyValue.substring(keyValue.indexOf("=") + 1);\r
1168       if (key.equalsIgnoreCase("scaletofit"))\r
1169       {\r
1170         scaletofit = Boolean.valueOf(value).booleanValue();\r
1171       }\r
1172       if (key.equalsIgnoreCase("showalllabs"))\r
1173       {\r
1174         showalllabs = Boolean.valueOf(value).booleanValue();\r
1175       }\r
1176       if (key.equalsIgnoreCase("centrelabs"))\r
1177       {\r
1178         centerlab = Boolean.valueOf(value).booleanValue();\r
1179       }\r
1180       AlignmentAnnotation[] alr = al.getAlignmentAnnotation();\r
1181       if (alr != null)\r
1182       {\r
1183         for (int i = 0; i < alr.length; i++)\r
1184         {\r
1185           if (alr[i].label.equalsIgnoreCase(label))\r
1186           {\r
1187             alr[i].centreColLabels = centerlab;\r
1188             alr[i].scaleColLabel = scaletofit;\r
1189             alr[i].showAllColLabels = showalllabs;\r
1190           }\r
1191         }\r
1192       }\r
1193     }\r
1194   }\r
1195 \r
1196   void addProperties(AlignmentI al, StringTokenizer st)\r
1197   {\r
1198 \r
1199     // So far we have only added groups to the annotationHash,\r
1200     // the idea is in the future properties can be added to\r
1201     // alignments, other annotations etc\r
1202     if (al.getGroups() == null)\r
1203     {\r
1204       return;\r
1205     }\r
1206     SequenceGroup sg = null;\r
1207 \r
1208     String name = st.nextToken();\r
1209 \r
1210     Vector groups = al.getGroups();\r
1211     for (int i = 0; i < groups.size(); i++)\r
1212     {\r
1213       sg = (SequenceGroup) groups.elementAt(i);\r
1214       if (sg.getName().equals(name))\r
1215       {\r
1216         break;\r
1217       }\r
1218       else\r
1219       {\r
1220         sg = null;\r
1221       }\r
1222     }\r
1223 \r
1224     if (sg != null)\r
1225     {\r
1226       String keyValue, key, value;\r
1227       ColourSchemeI def = sg.cs;\r
1228       sg.cs = null;\r
1229       while (st.hasMoreTokens())\r
1230       {\r
1231         keyValue = st.nextToken();\r
1232         key = keyValue.substring(0, keyValue.indexOf("="));\r
1233         value = keyValue.substring(keyValue.indexOf("=") + 1);\r
1234 \r
1235         if (key.equalsIgnoreCase("description"))\r
1236         {\r
1237           sg.setDescription(value);\r
1238         }\r
1239         else if (key.equalsIgnoreCase("colour"))\r
1240         {\r
1241           sg.cs = ColourSchemeProperty.getColour(al, value);\r
1242         }\r
1243         else if (key.equalsIgnoreCase("pidThreshold"))\r
1244         {\r
1245           sg.cs.setThreshold(Integer.parseInt(value), true);\r
1246 \r
1247         }\r
1248         else if (key.equalsIgnoreCase("consThreshold"))\r
1249         {\r
1250           sg.cs.setConservationInc(Integer.parseInt(value));\r
1251           Conservation c = new Conservation("Group",\r
1252                   ResidueProperties.propHash, 3, sg.getSequences(null),\r
1253                   sg.getStartRes(), sg.getEndRes() + 1);\r
1254 \r
1255           c.calculate();\r
1256           c.verdict(false, 25);\r
1257 \r
1258           sg.cs.setConservation(c);\r
1259 \r
1260         }\r
1261         else if (key.equalsIgnoreCase("outlineColour"))\r
1262         {\r
1263           sg.setOutlineColour(new UserColourScheme(value).findColour('A'));\r
1264         }\r
1265         else if (key.equalsIgnoreCase("displayBoxes"))\r
1266         {\r
1267           sg.setDisplayBoxes(Boolean.valueOf(value).booleanValue());\r
1268         }\r
1269         else if (key.equalsIgnoreCase("showUnconserved"))\r
1270         {\r
1271           sg.setShowNonconserved(Boolean.valueOf(value).booleanValue());\r
1272         }\r
1273         else if (key.equalsIgnoreCase("displayText"))\r
1274         {\r
1275           sg.setDisplayText(Boolean.valueOf(value).booleanValue());\r
1276         }\r
1277         else if (key.equalsIgnoreCase("colourText"))\r
1278         {\r
1279           sg.setColourText(Boolean.valueOf(value).booleanValue());\r
1280         }\r
1281         else if (key.equalsIgnoreCase("textCol1"))\r
1282         {\r
1283           sg.textColour = new UserColourScheme(value).findColour('A');\r
1284         }\r
1285         else if (key.equalsIgnoreCase("textCol2"))\r
1286         {\r
1287           sg.textColour2 = new UserColourScheme(value).findColour('A');\r
1288         }\r
1289         else if (key.equalsIgnoreCase("textColThreshold"))\r
1290         {\r
1291           sg.thresholdTextColour = Integer.parseInt(value);\r
1292         }\r
1293         else if (key.equalsIgnoreCase("idColour"))\r
1294         {\r
1295           // consider warning if colour doesn't resolve to a real colour\r
1296           sg.setIdColour((def = new UserColourScheme(value))\r
1297                   .findColour('A'));\r
1298         }\r
1299         else if (key.equalsIgnoreCase("hide"))\r
1300         {\r
1301           // see bug https://mantis.lifesci.dundee.ac.uk/view.php?id=25847\r
1302           sg.setHidereps(true);\r
1303         }\r
1304         else if (key.equalsIgnoreCase("hidecols"))\r
1305         {\r
1306           // see bug https://mantis.lifesci.dundee.ac.uk/view.php?id=25847\r
1307           sg.setHideCols(true);\r
1308         }\r
1309         sg.recalcConservation();\r
1310       }\r
1311       if (sg.cs == null)\r
1312       {\r
1313         sg.cs = def;\r
1314       }\r
1315     }\r
1316   }\r
1317 \r
1318   void setBelowAlignment(AlignmentI al, StringTokenizer st)\r
1319   {\r
1320     String token;\r
1321     AlignmentAnnotation aa, ala[] = al.getAlignmentAnnotation();\r
1322     if (ala == null)\r
1323     {\r
1324       System.err\r
1325               .print("Warning - no annotation to set below for sequence associated annotation:");\r
1326     }\r
1327     while (st.hasMoreTokens())\r
1328     {\r
1329       token = st.nextToken();\r
1330       if (ala == null)\r
1331       {\r
1332         System.err.print(" " + token);\r
1333       }\r
1334       else\r
1335       {\r
1336         for (int i = 0; i < al.getAlignmentAnnotation().length; i++)\r
1337         {\r
1338           aa = al.getAlignmentAnnotation()[i];\r
1339           if (aa.sequenceRef == refSeq && aa.label.equals(token))\r
1340           {\r
1341             aa.belowAlignment = true;\r
1342           }\r
1343         }\r
1344       }\r
1345     }\r
1346     if (ala == null)\r
1347     {\r
1348       System.err.print("\n");\r
1349     }\r
1350   }\r
1351 \r
1352   void addAlignmentDetails(AlignmentI al, StringTokenizer st)\r
1353   {\r
1354     String keyValue, key, value;\r
1355     while (st.hasMoreTokens())\r
1356     {\r
1357       keyValue = st.nextToken();\r
1358       key = keyValue.substring(0, keyValue.indexOf("="));\r
1359       value = keyValue.substring(keyValue.indexOf("=") + 1);\r
1360       al.setProperty(key, value);\r
1361     }\r
1362   }\r
1363 \r
1364   /**\r
1365    * Write annotations as a CSV file of the form 'label, value, value, ...' for\r
1366    * each row.\r
1367    * \r
1368    * @param annotations\r
1369    * @return CSV file as a string.\r
1370    */\r
1371   public String printCSVAnnotations(AlignmentAnnotation[] annotations)\r
1372   {\r
1373     StringBuffer sp = new StringBuffer();\r
1374     for (int i = 0; i < annotations.length; i++)\r
1375     {\r
1376       String atos = annotations[i].toString();\r
1377       int p = 0;\r
1378       do\r
1379       {\r
1380         int cp = atos.indexOf("\n", p);\r
1381         sp.append(annotations[i].label);\r
1382         sp.append(",");\r
1383         if (cp > p)\r
1384         {\r
1385           sp.append(atos.substring(p, cp + 1));\r
1386         }\r
1387         else\r
1388         {\r
1389           sp.append(atos.substring(p));\r
1390           sp.append("\n");\r
1391         }\r
1392         p = cp + 1;\r
1393       } while (p > 0);\r
1394     }\r
1395     return sp.toString();\r
1396   }\r
1397 }\r