Jalview 2.8 Source Header
[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       return false;
583     }
584     return false;
585   }
586
587   public boolean parseAnnotationFrom(AlignmentI al, BufferedReader in)
588           throws Exception
589   {
590     boolean modified = false;
591     String groupRef = null;
592     Hashtable groupRefRows = new Hashtable();
593
594     Hashtable autoAnnots = new Hashtable();
595     {
596       String line, label, description, token;
597       int graphStyle, index;
598       int refSeqIndex = 1;
599       int existingAnnotations = 0;
600       // when true - will add new rows regardless of whether they are duplicate
601       // auto-annotation like consensus or conservation graphs
602       boolean overrideAutoAnnot = false;
603       if (al.getAlignmentAnnotation() != null)
604       {
605         existingAnnotations = al.getAlignmentAnnotation().length;
606         if (existingAnnotations > 0)
607         {
608           AlignmentAnnotation[] aa = al.getAlignmentAnnotation();
609           for (int aai = 0; aai < aa.length; aai++)
610           {
611             if (aa[aai].autoCalculated)
612             {
613               // make a note of the name and description
614               autoAnnots.put(
615                       autoAnnotsKey(aa[aai], aa[aai].sequenceRef,
616                               (aa[aai].groupRef == null ? null
617                                       : aa[aai].groupRef.getName())),
618                       new Integer(1));
619             }
620           }
621         }
622       }
623
624       int alWidth = al.getWidth();
625
626       StringTokenizer st;
627       Annotation[] annotations;
628       AlignmentAnnotation annotation = null;
629
630       // First confirm this is an Annotation file
631       boolean jvAnnotationFile = false;
632       while ((line = in.readLine()) != null)
633       {
634         if (line.indexOf("#") == 0)
635         {
636           continue;
637         }
638
639         if (line.indexOf("JALVIEW_ANNOTATION") > -1)
640         {
641           jvAnnotationFile = true;
642           break;
643         }
644       }
645
646       if (!jvAnnotationFile)
647       {
648         in.close();
649         return false;
650       }
651
652       while ((line = in.readLine()) != null)
653       {
654         if (line.indexOf("#") == 0
655                 || line.indexOf("JALVIEW_ANNOTATION") > -1
656                 || line.length() == 0)
657         {
658           continue;
659         }
660
661         st = new StringTokenizer(line, "\t");
662         token = st.nextToken();
663         if (token.equalsIgnoreCase("COLOUR"))
664         {
665           // TODO: use graduated colour def'n here too
666           colourAnnotations(al, st.nextToken(), st.nextToken());
667           modified = true;
668           continue;
669         }
670
671         else if (token.equalsIgnoreCase("COMBINE"))
672         {
673           combineAnnotations(al, st);
674           modified = true;
675           continue;
676         }
677         else if (token.equalsIgnoreCase("ROWPROPERTIES"))
678         {
679           addRowProperties(al, st);
680           modified = true;
681           continue;
682         }
683         else if (token.equalsIgnoreCase("GRAPHLINE"))
684         {
685           addLine(al, st);
686           modified = true;
687           continue;
688         }
689
690         else if (token.equalsIgnoreCase("SEQUENCE_REF"))
691         {
692           if (st.hasMoreTokens())
693           {
694             refSeq = al.findName(refSeqId = st.nextToken());
695             if (refSeq == null)
696             {
697               refSeqId = null;
698             }
699             try
700             {
701               refSeqIndex = Integer.parseInt(st.nextToken());
702               if (refSeqIndex < 1)
703               {
704                 refSeqIndex = 1;
705                 System.out
706                         .println("WARNING: SEQUENCE_REF index must be > 0 in AnnotationFile");
707               }
708             } catch (Exception ex)
709             {
710               refSeqIndex = 1;
711             }
712           }
713           else
714           {
715             refSeq = null;
716             refSeqId = null;
717           }
718           continue;
719         }
720         else if (token.equalsIgnoreCase("GROUP_REF"))
721         {
722           // Group references could be forward or backwards, so they are
723           // resolved after the whole file is read in
724           groupRef = null;
725           if (st.hasMoreTokens())
726           {
727             groupRef = st.nextToken();
728             if (groupRef.length() < 1)
729             {
730               groupRef = null; // empty string
731             }
732             else
733             {
734               if (groupRefRows.get(groupRef) == null)
735               {
736                 groupRefRows.put(groupRef, new Vector());
737               }
738             }
739           }
740           continue;
741         }
742         else if (token.equalsIgnoreCase("SEQUENCE_GROUP"))
743         {
744           addGroup(al, st);
745           continue;
746         }
747
748         else if (token.equalsIgnoreCase("PROPERTIES"))
749         {
750           addProperties(al, st);
751           modified = true;
752           continue;
753         }
754
755         else if (token.equalsIgnoreCase("BELOW_ALIGNMENT"))
756         {
757           setBelowAlignment(al, st);
758           modified = true;
759           continue;
760         }
761         else if (token.equalsIgnoreCase("ALIGNMENT"))
762         {
763           addAlignmentDetails(al, st);
764           modified = true;
765           continue;
766         }
767
768         // Parse out the annotation row
769         graphStyle = AlignmentAnnotation.getGraphValueFromString(token);
770         label = st.nextToken();
771
772         index = 0;
773         annotations = new Annotation[alWidth];
774         description = null;
775         float score = Float.NaN;
776
777         if (st.hasMoreTokens())
778         {
779           line = st.nextToken();
780
781           if (line.indexOf("|") == -1)
782           {
783             description = line;
784             if (st.hasMoreTokens())
785               line = st.nextToken();
786           }
787
788           if (st.hasMoreTokens())
789           {
790             // This must be the score
791             score = Float.valueOf(st.nextToken()).floatValue();
792           }
793
794           st = new StringTokenizer(line, "|", true);
795
796           boolean emptyColumn = true;
797           boolean onlyOneElement = (st.countTokens() == 1);
798
799           while (st.hasMoreElements() && index < alWidth)
800           {
801             token = st.nextToken().trim();
802
803             if (onlyOneElement)
804             {
805               try
806               {
807                 score = Float.valueOf(token).floatValue();
808                 break;
809               } catch (NumberFormatException ex)
810               {
811               }
812             }
813
814             if (token.equals("|"))
815             {
816               if (emptyColumn)
817               {
818                 index++;
819               }
820
821               emptyColumn = true;
822             }
823             else
824             {
825               annotations[index++] = parseAnnotation(token, graphStyle);
826               emptyColumn = false;
827             }
828           }
829
830         }
831
832         annotation = new AlignmentAnnotation(label, description,
833                 (index == 0) ? null : annotations, 0, 0, graphStyle);
834
835         annotation.score = score;
836         if (!overrideAutoAnnot
837                 && autoAnnots.containsKey(autoAnnotsKey(annotation, refSeq,
838                         groupRef)))
839         {
840           // skip - we've already got an automatic annotation of this type.
841           continue;
842         }
843         // otherwise add it!
844         if (refSeq != null)
845         {
846
847           annotation.belowAlignment = false;
848           // make a copy of refSeq so we can find other matches in the alignment
849           SequenceI referedSeq = refSeq;
850           do
851           {
852             // copy before we do any mapping business.
853             // TODO: verify that undo/redo with 1:many sequence associated
854             // annotations can be undone correctly
855             AlignmentAnnotation ann = new AlignmentAnnotation(annotation);
856             annotation
857                     .createSequenceMapping(referedSeq, refSeqIndex, false);
858             annotation.adjustForAlignment();
859             referedSeq.addAlignmentAnnotation(annotation);
860             al.addAnnotation(annotation);
861             al.setAnnotationIndex(annotation,
862                     al.getAlignmentAnnotation().length
863                             - existingAnnotations - 1);
864             if (groupRef != null)
865             {
866               ((Vector) groupRefRows.get(groupRef)).addElement(annotation);
867             }
868             // and recover our virgin copy to use again if necessary.
869             annotation = ann;
870
871           } while (refSeqId != null
872                   && (referedSeq = al.findName(referedSeq, refSeqId, true)) != null);
873         }
874         else
875         {
876           al.addAnnotation(annotation);
877           al.setAnnotationIndex(annotation,
878                   al.getAlignmentAnnotation().length - existingAnnotations
879                           - 1);
880           if (groupRef != null)
881           {
882             ((Vector) groupRefRows.get(groupRef)).addElement(annotation);
883           }
884         }
885         // and set modification flag
886         modified = true;
887       }
888       // Finally, resolve the groupRefs
889       Enumeration en = groupRefRows.keys();
890
891       while (en.hasMoreElements())
892       {
893         groupRef = (String) en.nextElement();
894         boolean matched = false;
895         // Resolve group: TODO: add a getGroupByName method to alignments
896         for (SequenceGroup theGroup : al.getGroups())
897         {
898           if (theGroup.getName().equals(groupRef))
899           {
900             if (matched)
901             {
902               // TODO: specify and implement duplication of alignment annotation
903               // for multiple group references.
904               System.err
905                       .println("Ignoring 1:many group reference mappings for group name '"
906                               + groupRef + "'");
907             }
908             else
909             {
910               matched = true;
911               Vector rowset = (Vector) groupRefRows.get(groupRef);
912               if (rowset != null && rowset.size() > 0)
913               {
914                 AlignmentAnnotation alan = null;
915                 for (int elm = 0, elmSize = rowset.size(); elm < elmSize; elm++)
916                 {
917                   alan = (AlignmentAnnotation) rowset.elementAt(elm);
918                   alan.groupRef = theGroup;
919                 }
920               }
921             }
922           }
923         }
924         ((Vector) groupRefRows.get(groupRef)).removeAllElements();
925       }
926     }
927     return modified;
928   }
929
930   private Object autoAnnotsKey(AlignmentAnnotation annotation,
931           SequenceI refSeq, String groupRef)
932   {
933     return annotation.graph + "\t" + annotation.label + "\t"
934             + annotation.description + "\t"
935             + (refSeq != null ? refSeq.getDisplayId(true) : "");
936   }
937
938   Annotation parseAnnotation(String string, int graphStyle)
939   {
940     boolean hasSymbols = (graphStyle == AlignmentAnnotation.NO_GRAPH); // don't
941     // do the
942     // glyph
943     // test
944     // if we
945     // don't
946     // want
947     // secondary
948     // structure
949     String desc = null, displayChar = null;
950     char ss = ' '; // secondaryStructure
951     float value = 0;
952     boolean parsedValue = false, dcset = false;
953
954     // find colour here
955     java.awt.Color colour = null;
956     int i = string.indexOf("[");
957     int j = string.indexOf("]");
958     if (i > -1 && j > -1)
959     {
960       UserColourScheme ucs = new UserColourScheme();
961
962       colour = ucs.getColourFromString(string.substring(i + 1, j));
963       if (i > 0 && string.charAt(i - 1) == ',')
964       {
965         // clip the preceding comma as well
966         i--;
967       }
968       string = string.substring(0, i) + string.substring(j + 1);
969     }
970
971     StringTokenizer st = new StringTokenizer(string, ",", true);
972     String token;
973     boolean seenContent = false;
974     int pass = 0;
975     while (st.hasMoreTokens())
976     {
977       pass++;
978       token = st.nextToken().trim();
979       if (token.equals(","))
980       {
981         if (!seenContent && parsedValue && !dcset)
982         {
983           // allow the value below the bar/line to be empty
984           dcset = true;
985           displayChar = " ";
986         }
987         seenContent = false;
988         continue;
989       }
990       else
991       {
992         seenContent = true;
993       }
994
995       if (!parsedValue)
996       {
997         try
998         {
999           displayChar = token;
1000           // foo
1001           value = new Float(token).floatValue();
1002           parsedValue = true;
1003           continue;
1004         } catch (NumberFormatException ex)
1005         {
1006         }
1007       }
1008       else
1009       {
1010         if (token.length() == 1)
1011         {
1012           displayChar = token;
1013         }
1014       }
1015       if (hasSymbols
1016               && (token.equals("H") || token.equals("E")
1017                       || token.equals("S") || token.equals(" ")))
1018       {
1019         // Either this character represents a helix or sheet
1020         // or an integer which can be displayed
1021         ss = token.charAt(0);
1022         if (displayChar.equals(token.substring(0, 1)))
1023         {
1024           displayChar = "";
1025         }
1026       }
1027       else if (desc == null || (parsedValue && pass > 2))
1028       {
1029         desc = token;
1030       }
1031
1032     }
1033     // if (!dcset && string.charAt(string.length() - 1) == ',')
1034     // {
1035     // displayChar = " "; // empty display char symbol.
1036     // }
1037     if (displayChar != null && desc != null && desc.length() == 1)
1038     {
1039       if (displayChar.length() > 1)
1040       {
1041         // switch desc and displayChar - legacy support
1042         String tmp = displayChar;
1043         displayChar = desc;
1044         desc = tmp;
1045       }
1046       else
1047       {
1048         if (displayChar.equals(desc))
1049         {
1050           // duplicate label - hangover from the 'robust parser' above
1051           desc = null;
1052         }
1053       }
1054     }
1055     Annotation anot = new Annotation(displayChar, desc, ss, value);
1056
1057     anot.colour = colour;
1058
1059     return anot;
1060   }
1061
1062   void colourAnnotations(AlignmentI al, String label, String colour)
1063   {
1064     UserColourScheme ucs = new UserColourScheme(colour);
1065     Annotation[] annotations;
1066     for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1067     {
1068       if (al.getAlignmentAnnotation()[i].label.equalsIgnoreCase(label))
1069       {
1070         annotations = al.getAlignmentAnnotation()[i].annotations;
1071         for (int j = 0; j < annotations.length; j++)
1072         {
1073           if (annotations[j] != null)
1074           {
1075             annotations[j].colour = ucs.findColour('A');
1076           }
1077         }
1078       }
1079     }
1080   }
1081
1082   void combineAnnotations(AlignmentI al, StringTokenizer st)
1083   {
1084     int graphGroup = -1;
1085     String group = st.nextToken();
1086     // First make sure we are not overwriting the graphIndex
1087     if (al.getAlignmentAnnotation() != null)
1088     {
1089       for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1090       {
1091         if (al.getAlignmentAnnotation()[i].label.equalsIgnoreCase(group))
1092         {
1093           graphGroup = al.getAlignmentAnnotation()[i].graphGroup + 1;
1094           al.getAlignmentAnnotation()[i].graphGroup = graphGroup;
1095           break;
1096         }
1097       }
1098
1099       // Now update groups
1100       while (st.hasMoreTokens())
1101       {
1102         group = st.nextToken();
1103         for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1104         {
1105           if (al.getAlignmentAnnotation()[i].label.equalsIgnoreCase(group))
1106           {
1107             al.getAlignmentAnnotation()[i].graphGroup = graphGroup;
1108             break;
1109           }
1110         }
1111       }
1112     }
1113     else
1114     {
1115       System.err
1116               .println("Couldn't combine annotations. None are added to alignment yet!");
1117     }
1118   }
1119
1120   void addLine(AlignmentI al, StringTokenizer st)
1121   {
1122     String group = st.nextToken();
1123     AlignmentAnnotation annotation = null, alannot[] = al
1124             .getAlignmentAnnotation();
1125     if (alannot != null)
1126     {
1127       for (int i = 0; i < alannot.length; i++)
1128       {
1129         if (alannot[i].label.equalsIgnoreCase(group))
1130         {
1131           annotation = alannot[i];
1132           break;
1133         }
1134       }
1135     }
1136     if (annotation == null)
1137     {
1138       return;
1139     }
1140     float value = new Float(st.nextToken()).floatValue();
1141     String label = st.hasMoreTokens() ? st.nextToken() : null;
1142     java.awt.Color colour = null;
1143     if (st.hasMoreTokens())
1144     {
1145       UserColourScheme ucs = new UserColourScheme(st.nextToken());
1146       colour = ucs.findColour('A');
1147     }
1148
1149     annotation.setThreshold(new GraphLine(value, label, colour));
1150   }
1151
1152   void addGroup(AlignmentI al, StringTokenizer st)
1153   {
1154     SequenceGroup sg = new SequenceGroup();
1155     sg.setName(st.nextToken());
1156     String rng = "";
1157     try
1158     {
1159       rng = st.nextToken();
1160       if (rng.length() > 0 && !rng.startsWith("*"))
1161       {
1162         sg.setStartRes(Integer.parseInt(rng) - 1);
1163       }
1164       else
1165       {
1166         sg.setStartRes(0);
1167       }
1168       rng = st.nextToken();
1169       if (rng.length() > 0 && !rng.startsWith("*"))
1170       {
1171         sg.setEndRes(Integer.parseInt(rng) - 1);
1172       }
1173       else
1174       {
1175         sg.setEndRes(al.getWidth() - 1);
1176       }
1177     } catch (Exception e)
1178     {
1179       System.err
1180               .println("Couldn't parse Group Start or End Field as '*' or a valid column or sequence index: '"
1181                       + rng + "' - assuming alignment width for group.");
1182       // assume group is full width
1183       sg.setStartRes(0);
1184       sg.setEndRes(al.getWidth() - 1);
1185     }
1186
1187     String index = st.nextToken();
1188     if (index.equals("-1"))
1189     {
1190       while (st.hasMoreElements())
1191       {
1192         sg.addSequence(al.findName(st.nextToken()), false);
1193       }
1194     }
1195     else
1196     {
1197       StringTokenizer st2 = new StringTokenizer(index, ",");
1198
1199       while (st2.hasMoreTokens())
1200       {
1201         String tmp = st2.nextToken();
1202         if (tmp.equals("*"))
1203         {
1204           for (int i = 0; i < al.getHeight(); i++)
1205           {
1206             sg.addSequence(al.getSequenceAt(i), false);
1207           }
1208         }
1209         else if (tmp.indexOf("-") >= 0)
1210         {
1211           StringTokenizer st3 = new StringTokenizer(tmp, "-");
1212
1213           int start = (Integer.parseInt(st3.nextToken()));
1214           int end = (Integer.parseInt(st3.nextToken()));
1215
1216           if (end > start)
1217           {
1218             for (int i = start; i <= end; i++)
1219             {
1220               sg.addSequence(al.getSequenceAt(i - 1), false);
1221             }
1222           }
1223         }
1224         else
1225         {
1226           sg.addSequence(al.getSequenceAt(Integer.parseInt(tmp) - 1), false);
1227         }
1228       }
1229     }
1230
1231     if (refSeq != null)
1232     {
1233       sg.setStartRes(refSeq.findIndex(sg.getStartRes() + 1) - 1);
1234       sg.setEndRes(refSeq.findIndex(sg.getEndRes() + 1) - 1);
1235       sg.setSeqrep(refSeq);
1236     }
1237
1238     if (sg.getSize() > 0)
1239     {
1240       al.addGroup(sg);
1241     }
1242   }
1243
1244   void addRowProperties(AlignmentI al, StringTokenizer st)
1245   {
1246     String label = st.nextToken(), keyValue, key, value;
1247     boolean scaletofit = false, centerlab = false, showalllabs = false;
1248     while (st.hasMoreTokens())
1249     {
1250       keyValue = st.nextToken();
1251       key = keyValue.substring(0, keyValue.indexOf("="));
1252       value = keyValue.substring(keyValue.indexOf("=") + 1);
1253       if (key.equalsIgnoreCase("scaletofit"))
1254       {
1255         scaletofit = Boolean.valueOf(value).booleanValue();
1256       }
1257       if (key.equalsIgnoreCase("showalllabs"))
1258       {
1259         showalllabs = Boolean.valueOf(value).booleanValue();
1260       }
1261       if (key.equalsIgnoreCase("centrelabs"))
1262       {
1263         centerlab = Boolean.valueOf(value).booleanValue();
1264       }
1265       AlignmentAnnotation[] alr = al.getAlignmentAnnotation();
1266       if (alr != null)
1267       {
1268         for (int i = 0; i < alr.length; i++)
1269         {
1270           if (alr[i].label.equalsIgnoreCase(label))
1271           {
1272             alr[i].centreColLabels = centerlab;
1273             alr[i].scaleColLabel = scaletofit;
1274             alr[i].showAllColLabels = showalllabs;
1275           }
1276         }
1277       }
1278     }
1279   }
1280
1281   void addProperties(AlignmentI al, StringTokenizer st)
1282   {
1283
1284     // So far we have only added groups to the annotationHash,
1285     // the idea is in the future properties can be added to
1286     // alignments, other annotations etc
1287     if (al.getGroups() == null)
1288     {
1289       return;
1290     }
1291
1292     String name = st.nextToken();
1293     SequenceGroup sg = null;
1294     for (SequenceGroup _sg : al.getGroups())
1295     {
1296       if ((sg = _sg).getName().equals(name))
1297       {
1298         break;
1299       }
1300       else
1301       {
1302         sg = null;
1303       }
1304     }
1305
1306     if (sg != null)
1307     {
1308       String keyValue, key, value;
1309       ColourSchemeI def = sg.cs;
1310       sg.cs = null;
1311       while (st.hasMoreTokens())
1312       {
1313         keyValue = st.nextToken();
1314         key = keyValue.substring(0, keyValue.indexOf("="));
1315         value = keyValue.substring(keyValue.indexOf("=") + 1);
1316
1317         if (key.equalsIgnoreCase("description"))
1318         {
1319           sg.setDescription(value);
1320         }
1321         else if (key.equalsIgnoreCase("colour"))
1322         {
1323           sg.cs = ColourSchemeProperty.getColour(al, value);
1324         }
1325         else if (key.equalsIgnoreCase("pidThreshold"))
1326         {
1327           sg.cs.setThreshold(Integer.parseInt(value), true);
1328
1329         }
1330         else if (key.equalsIgnoreCase("consThreshold"))
1331         {
1332           sg.cs.setConservationInc(Integer.parseInt(value));
1333           Conservation c = new Conservation("Group",
1334                   ResidueProperties.propHash, 3, sg.getSequences(null),
1335                   sg.getStartRes(), sg.getEndRes() + 1);
1336
1337           c.calculate();
1338           c.verdict(false, 25); // TODO: refer to conservation percent threshold
1339
1340           sg.cs.setConservation(c);
1341
1342         }
1343         else if (key.equalsIgnoreCase("outlineColour"))
1344         {
1345           sg.setOutlineColour(new UserColourScheme(value).findColour('A'));
1346         }
1347         else if (key.equalsIgnoreCase("displayBoxes"))
1348         {
1349           sg.setDisplayBoxes(Boolean.valueOf(value).booleanValue());
1350         }
1351         else if (key.equalsIgnoreCase("showUnconserved"))
1352         {
1353           sg.setShowNonconserved(Boolean.valueOf(value).booleanValue());
1354         }
1355         else if (key.equalsIgnoreCase("displayText"))
1356         {
1357           sg.setDisplayText(Boolean.valueOf(value).booleanValue());
1358         }
1359         else if (key.equalsIgnoreCase("colourText"))
1360         {
1361           sg.setColourText(Boolean.valueOf(value).booleanValue());
1362         }
1363         else if (key.equalsIgnoreCase("textCol1"))
1364         {
1365           sg.textColour = new UserColourScheme(value).findColour('A');
1366         }
1367         else if (key.equalsIgnoreCase("textCol2"))
1368         {
1369           sg.textColour2 = new UserColourScheme(value).findColour('A');
1370         }
1371         else if (key.equalsIgnoreCase("textColThreshold"))
1372         {
1373           sg.thresholdTextColour = Integer.parseInt(value);
1374         }
1375         else if (key.equalsIgnoreCase("idColour"))
1376         {
1377           // consider warning if colour doesn't resolve to a real colour
1378           sg.setIdColour((def = new UserColourScheme(value))
1379                   .findColour('A'));
1380         }
1381         else if (key.equalsIgnoreCase("hide"))
1382         {
1383           // see bug https://mantis.lifesci.dundee.ac.uk/view.php?id=25847
1384           sg.setHidereps(true);
1385         }
1386         else if (key.equalsIgnoreCase("hidecols"))
1387         {
1388           // see bug https://mantis.lifesci.dundee.ac.uk/view.php?id=25847
1389           sg.setHideCols(true);
1390         }
1391         sg.recalcConservation();
1392       }
1393       if (sg.cs == null)
1394       {
1395         sg.cs = def;
1396       }
1397     }
1398   }
1399
1400   void setBelowAlignment(AlignmentI al, StringTokenizer st)
1401   {
1402     String token;
1403     AlignmentAnnotation aa, ala[] = al.getAlignmentAnnotation();
1404     if (ala == null)
1405     {
1406       System.err
1407               .print("Warning - no annotation to set below for sequence associated annotation:");
1408     }
1409     while (st.hasMoreTokens())
1410     {
1411       token = st.nextToken();
1412       if (ala == null)
1413       {
1414         System.err.print(" " + token);
1415       }
1416       else
1417       {
1418         for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1419         {
1420           aa = al.getAlignmentAnnotation()[i];
1421           if (aa.sequenceRef == refSeq && aa.label.equals(token))
1422           {
1423             aa.belowAlignment = true;
1424           }
1425         }
1426       }
1427     }
1428     if (ala == null)
1429     {
1430       System.err.print("\n");
1431     }
1432   }
1433
1434   void addAlignmentDetails(AlignmentI al, StringTokenizer st)
1435   {
1436     String keyValue, key, value;
1437     while (st.hasMoreTokens())
1438     {
1439       keyValue = st.nextToken();
1440       key = keyValue.substring(0, keyValue.indexOf("="));
1441       value = keyValue.substring(keyValue.indexOf("=") + 1);
1442       al.setProperty(key, value);
1443     }
1444   }
1445
1446   /**
1447    * Write annotations as a CSV file of the form 'label, value, value, ...' for
1448    * each row.
1449    * 
1450    * @param annotations
1451    * @return CSV file as a string.
1452    */
1453   public String printCSVAnnotations(AlignmentAnnotation[] annotations)
1454   {
1455     StringBuffer sp = new StringBuffer();
1456     for (int i = 0; i < annotations.length; i++)
1457     {
1458       String atos = annotations[i].toString();
1459       int p = 0;
1460       do
1461       {
1462         int cp = atos.indexOf("\n", p);
1463         sp.append(annotations[i].label);
1464         sp.append(",");
1465         if (cp > p)
1466         {
1467           sp.append(atos.substring(p, cp + 1));
1468         }
1469         else
1470         {
1471           sp.append(atos.substring(p));
1472           sp.append(newline);
1473         }
1474         p = cp + 1;
1475       } while (p > 0);
1476     }
1477     return sp.toString();
1478   }
1479 }