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