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