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