JAL-2137 basic implementation of STRUCTMODEL annotation file statement
[jalview.git] / src / jalview / io / AnnotationFile.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ 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
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.io;
22
23 import jalview.analysis.Conservation;
24 import jalview.api.AlignViewportI;
25 import jalview.datamodel.AlignmentAnnotation;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.Annotation;
28 import jalview.datamodel.ColumnSelection;
29 import jalview.datamodel.GraphLine;
30 import jalview.datamodel.HiddenSequences;
31 import jalview.datamodel.PDBEntry;
32 import jalview.datamodel.PDBEntry.Type;
33 import jalview.datamodel.SequenceGroup;
34 import jalview.datamodel.SequenceI;
35 import jalview.schemes.ColourSchemeI;
36 import jalview.schemes.ColourSchemeProperty;
37 import jalview.schemes.ResidueProperties;
38 import jalview.schemes.UserColourScheme;
39
40 import java.io.BufferedReader;
41 import java.io.FileReader;
42 import java.io.IOException;
43 import java.io.InputStreamReader;
44 import java.io.StringReader;
45 import java.net.URL;
46 import java.util.ArrayList;
47 import java.util.BitSet;
48 import java.util.Enumeration;
49 import java.util.Hashtable;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.StringTokenizer;
53 import java.util.Vector;
54
55 public class AnnotationFile
56 {
57   public AnnotationFile()
58   {
59     init();
60   }
61
62   /**
63    * character used to write newlines
64    */
65   protected String newline = System.getProperty("line.separator");
66
67   /**
68    * set new line string and reset the output buffer
69    * 
70    * @param nl
71    */
72   public void setNewlineString(String nl)
73   {
74     newline = nl;
75     init();
76   }
77
78   public String getNewlineString()
79   {
80     return newline;
81   }
82
83   StringBuffer text;
84
85   private void init()
86   {
87     text = new StringBuffer("JALVIEW_ANNOTATION" + newline + "# Created: "
88             + new java.util.Date() + newline + newline);
89     refSeq = null;
90     refSeqId = null;
91   }
92
93   /**
94    * convenience method for pre-2.9 annotation files which have no view, hidden
95    * columns or hidden row keywords.
96    * 
97    * @param annotations
98    * @param list
99    * @param properties
100    * @return annotation file as a string.
101    */
102   public String printAnnotations(AlignmentAnnotation[] annotations,
103           List<SequenceGroup> list, Hashtable properties)
104   {
105     return printAnnotations(annotations, list, properties, null, null, null);
106
107   }
108
109   /**
110    * hold all the information about a particular view definition read from or
111    * written out in an annotations file.
112    */
113   public class ViewDef
114   {
115     public String viewname;
116
117     public HiddenSequences hidseqs;
118
119     public ColumnSelection hiddencols;
120
121     public Vector visibleGroups;
122
123     public Hashtable hiddenRepSeqs;
124
125     public ViewDef(String viewname, HiddenSequences hidseqs,
126             ColumnSelection hiddencols, Hashtable hiddenRepSeqs)
127     {
128       this.viewname = viewname;
129       this.hidseqs = hidseqs;
130       this.hiddencols = hiddencols;
131       this.hiddenRepSeqs = hiddenRepSeqs;
132     }
133   }
134
135   /**
136    * Prepare an annotation file given a set of annotations, groups, alignment
137    * properties and views.
138    * 
139    * @param annotations
140    * @param list
141    * @param properties
142    * @param views
143    * @return annotation file
144    */
145   public String printAnnotations(AlignmentAnnotation[] annotations,
146           List<SequenceGroup> list, Hashtable properties,
147           ColumnSelection cs, AlignmentI al, ViewDef view)
148   {
149     if (view != null)
150     {
151       if (view.viewname != null)
152       {
153         text.append("VIEW_DEF\t" + view.viewname + "\n");
154       }
155       if (list == null)
156       {
157         list = view.visibleGroups;
158       }
159       if (cs == null)
160       {
161         cs = view.hiddencols;
162       }
163       if (al == null)
164       {
165         // add hidden rep sequences.
166       }
167     }
168     // first target - store and restore all settings for a view.
169     if (al != null && al.hasSeqrep())
170     {
171       text.append("VIEW_SETREF\t" + al.getSeqrep().getName() + "\n");
172     }
173     if (cs != null && cs.hasHiddenColumns())
174     {
175       text.append("VIEW_HIDECOLS\t");
176       List<int[]> hc = cs.getHiddenColumns();
177       boolean comma = false;
178       for (int[] r : hc)
179       {
180         if (!comma)
181         {
182           comma = true;
183         }
184         else
185         {
186           text.append(",");
187         }
188         text.append(r[0]);
189         text.append("-");
190         text.append(r[1]);
191       }
192       text.append("\n");
193     }
194     // TODO: allow efficient recovery of annotation data shown in several
195     // different views
196     if (annotations != null)
197     {
198       boolean oneColour = true;
199       AlignmentAnnotation row;
200       String comma;
201       SequenceI refSeq = null;
202       SequenceGroup refGroup = null;
203
204       StringBuffer colours = new StringBuffer();
205       StringBuffer graphLine = new StringBuffer();
206       StringBuffer rowprops = new StringBuffer();
207       Hashtable<Integer, String> graphGroup = new Hashtable<Integer, String>();
208       Hashtable<Integer, Object[]> graphGroup_refs = new Hashtable<Integer, Object[]>();
209       BitSet graphGroupSeen = new BitSet();
210
211       java.awt.Color color;
212
213       for (int i = 0; i < annotations.length; i++)
214       {
215         row = annotations[i];
216
217         if (!row.visible
218                 && !row.hasScore()
219                 && !(row.graphGroup > -1 && graphGroupSeen
220                         .get(row.graphGroup)))
221         {
222           continue;
223         }
224
225         color = null;
226         oneColour = true;
227
228         // mark any sequence references for the row
229         writeSequence_Ref(refSeq, row.sequenceRef);
230         refSeq = row.sequenceRef;
231         // mark any group references for the row
232         writeGroup_Ref(refGroup, row.groupRef);
233         refGroup = row.groupRef;
234
235         boolean hasGlyphs = row.hasIcons, hasLabels = row.hasText, hasValues = row.hasScore, hasText = false;
236         // lookahead to check what the annotation row object actually contains.
237         for (int j = 0; row.annotations != null
238                 && j < row.annotations.length
239                 && (!hasGlyphs || !hasLabels || !hasValues); j++)
240         {
241           if (row.annotations[j] != null)
242           {
243             hasLabels |= (row.annotations[j].displayCharacter != null
244                     && row.annotations[j].displayCharacter.length() > 0 && !row.annotations[j].displayCharacter
245                     .equals(" "));
246             hasGlyphs |= (row.annotations[j].secondaryStructure != 0 && row.annotations[j].secondaryStructure != ' ');
247             hasValues |= (!Float.isNaN(row.annotations[j].value)); // NaNs can't
248             // be
249             // rendered..
250             hasText |= (row.annotations[j].description != null && row.annotations[j].description
251                     .length() > 0);
252           }
253         }
254
255         if (row.graph == AlignmentAnnotation.NO_GRAPH)
256         {
257           text.append("NO_GRAPH\t");
258           hasValues = false; // only secondary structure
259           // hasLabels = false; // and annotation description string.
260         }
261         else
262         {
263           if (row.graph == AlignmentAnnotation.BAR_GRAPH)
264           {
265             text.append("BAR_GRAPH\t");
266             hasGlyphs = false; // no secondary structure
267
268           }
269           else if (row.graph == AlignmentAnnotation.LINE_GRAPH)
270           {
271             hasGlyphs = false; // no secondary structure
272             text.append("LINE_GRAPH\t");
273           }
274
275           if (row.getThreshold() != null)
276           {
277             graphLine.append("GRAPHLINE\t");
278             graphLine.append(row.label);
279             graphLine.append("\t");
280             graphLine.append(row.getThreshold().value);
281             graphLine.append("\t");
282             graphLine.append(row.getThreshold().label);
283             graphLine.append("\t");
284             graphLine.append(jalview.util.Format.getHexString(row
285                     .getThreshold().colour));
286             graphLine.append(newline);
287           }
288
289           if (row.graphGroup > -1)
290           {
291             graphGroupSeen.set(row.graphGroup);
292             Integer key = new Integer(row.graphGroup);
293             if (graphGroup.containsKey(key))
294             {
295               graphGroup.put(key, graphGroup.get(key) + "\t" + row.label);
296
297             }
298             else
299             {
300               graphGroup_refs.put(key, new Object[] { refSeq, refGroup });
301               graphGroup.put(key, row.label);
302             }
303           }
304         }
305
306         text.append(row.label + "\t");
307         if (row.description != null)
308         {
309           text.append(row.description + "\t");
310         }
311         for (int j = 0; row.annotations != null
312                 && j < row.annotations.length; j++)
313         {
314           if (refSeq != null
315                   && jalview.util.Comparison.isGap(refSeq.getCharAt(j)))
316           {
317             continue;
318           }
319
320           if (row.annotations[j] != null)
321           {
322             comma = "";
323             if (hasGlyphs) // could be also hasGlyphs || ...
324             {
325
326               text.append(comma);
327               if (row.annotations[j].secondaryStructure != ' ')
328               {
329                 // only write out the field if its not whitespace.
330                 text.append(row.annotations[j].secondaryStructure);
331               }
332               comma = ",";
333             }
334             if (hasValues)
335             {
336               if (!Float.isNaN(row.annotations[j].value))
337               {
338                 text.append(comma + row.annotations[j].value);
339               }
340               else
341               {
342                 // System.err.println("Skipping NaN - not valid value.");
343                 text.append(comma + 0f);// row.annotations[j].value);
344               }
345               comma = ",";
346             }
347             if (hasLabels)
348             {
349               // TODO: labels are emitted after values for bar graphs.
350               if // empty labels are allowed, so
351               (row.annotations[j].displayCharacter != null
352                       && row.annotations[j].displayCharacter.length() > 0
353                       && !row.annotations[j].displayCharacter.equals(" "))
354               {
355                 text.append(comma + row.annotations[j].displayCharacter);
356                 comma = ",";
357               }
358             }
359             if (hasText)
360             {
361               if (row.annotations[j].description != null
362                       && row.annotations[j].description.length() > 0
363                       && !row.annotations[j].description
364                               .equals(row.annotations[j].displayCharacter))
365               {
366                 text.append(comma + row.annotations[j].description);
367                 comma = ",";
368               }
369             }
370             if (color != null && !color.equals(row.annotations[j].colour))
371             {
372               oneColour = false;
373             }
374
375             color = row.annotations[j].colour;
376
377             if (row.annotations[j].colour != null
378                     && row.annotations[j].colour != java.awt.Color.black)
379             {
380               text.append(comma
381                       + "["
382                       + jalview.util.Format
383                               .getHexString(row.annotations[j].colour)
384                       + "]");
385               comma = ",";
386             }
387           }
388           text.append("|");
389         }
390
391         if (row.hasScore())
392         {
393           text.append("\t" + row.score);
394         }
395
396         text.append(newline);
397
398         if (color != null && color != java.awt.Color.black && oneColour)
399         {
400           colours.append("COLOUR\t");
401           colours.append(row.label);
402           colours.append("\t");
403           colours.append(jalview.util.Format.getHexString(color));
404           colours.append(newline);
405         }
406         if (row.scaleColLabel || row.showAllColLabels
407                 || row.centreColLabels)
408         {
409           rowprops.append("ROWPROPERTIES\t");
410           rowprops.append(row.label);
411           rowprops.append("\tscaletofit=");
412           rowprops.append(row.scaleColLabel);
413           rowprops.append("\tshowalllabs=");
414           rowprops.append(row.showAllColLabels);
415           rowprops.append("\tcentrelabs=");
416           rowprops.append(row.centreColLabels);
417           rowprops.append(newline);
418         }
419         if (graphLine.length() > 0)
420         {
421           text.append(graphLine.toString());
422           graphLine.setLength(0);
423         }
424       }
425
426       text.append(newline);
427
428       text.append(colours.toString());
429       if (graphGroup.size() > 0)
430       {
431         SequenceI oldRefSeq = refSeq;
432         SequenceGroup oldRefGroup = refGroup;
433         for (Map.Entry<Integer, String> combine_statement : graphGroup
434                 .entrySet())
435         {
436           Object[] seqRefAndGroup = graphGroup_refs.get(combine_statement
437                   .getKey());
438
439           writeSequence_Ref(refSeq, (SequenceI) seqRefAndGroup[0]);
440           refSeq = (SequenceI) seqRefAndGroup[0];
441
442           writeGroup_Ref(refGroup, (SequenceGroup) seqRefAndGroup[1]);
443           refGroup = (SequenceGroup) seqRefAndGroup[1];
444           text.append("COMBINE\t");
445           text.append(combine_statement.getValue());
446           text.append(newline);
447         }
448         writeSequence_Ref(refSeq, oldRefSeq);
449         refSeq = oldRefSeq;
450
451         writeGroup_Ref(refGroup, oldRefGroup);
452         refGroup = oldRefGroup;
453       }
454       text.append(rowprops.toString());
455     }
456
457     if (list != null)
458     {
459       printGroups(list);
460     }
461
462     if (properties != null)
463     {
464       text.append(newline);
465       text.append(newline);
466       text.append("ALIGNMENT");
467       Enumeration en = properties.keys();
468       while (en.hasMoreElements())
469       {
470         String key = en.nextElement().toString();
471         text.append("\t");
472         text.append(key);
473         text.append("=");
474         text.append(properties.get(key));
475       }
476       // TODO: output alignment visualization settings here if required
477       // iterate through one or more views, defining, marking columns and rows
478       // as visible/hidden, and emmitting view properties.
479       // View specific annotation is
480     }
481
482     return text.toString();
483   }
484
485   private Object writeGroup_Ref(SequenceGroup refGroup,
486           SequenceGroup next_refGroup)
487   {
488     if (next_refGroup == null)
489     {
490
491       if (refGroup != null)
492       {
493         text.append(newline);
494         text.append("GROUP_REF\t");
495         text.append("ALIGNMENT");
496         text.append(newline);
497       }
498       return true;
499     }
500     else
501     {
502       if (refGroup == null || refGroup != next_refGroup)
503       {
504         text.append(newline);
505         text.append("GROUP_REF\t");
506         text.append(next_refGroup.getName());
507         text.append(newline);
508         return true;
509       }
510     }
511     return false;
512   }
513
514   private boolean writeSequence_Ref(SequenceI refSeq, SequenceI next_refSeq)
515   {
516
517     if (next_refSeq == null)
518     {
519       if (refSeq != null)
520       {
521         text.append(newline);
522         text.append("SEQUENCE_REF\t");
523         text.append("ALIGNMENT");
524         text.append(newline);
525         return true;
526       }
527     }
528     else
529     {
530       if (refSeq == null || refSeq != next_refSeq)
531       {
532         text.append(newline);
533         text.append("SEQUENCE_REF\t");
534         text.append(next_refSeq.getName());
535         text.append(newline);
536         return true;
537       }
538     }
539     return false;
540   }
541
542   public void printGroups(List<SequenceGroup> list)
543   {
544     SequenceI seqrep = null;
545     for (SequenceGroup sg : list)
546     {
547       if (!sg.hasSeqrep())
548       {
549         text.append("SEQUENCE_GROUP\t" + sg.getName() + "\t"
550                 + (sg.getStartRes() + 1) + "\t" + (sg.getEndRes() + 1)
551                 + "\t" + "-1\t");
552         seqrep = null;
553       }
554       else
555       {
556         seqrep = sg.getSeqrep();
557         text.append("SEQUENCE_REF\t");
558         text.append(seqrep.getName());
559         text.append(newline);
560         text.append("SEQUENCE_GROUP\t");
561         text.append(sg.getName());
562         text.append("\t");
563         text.append((seqrep.findPosition(sg.getStartRes())));
564         text.append("\t");
565         text.append((seqrep.findPosition(sg.getEndRes())));
566         text.append("\t");
567         text.append("-1\t");
568       }
569       for (int s = 0; s < sg.getSize(); s++)
570       {
571         text.append(sg.getSequenceAt(s).getName());
572         text.append("\t");
573       }
574       text.append(newline);
575       text.append("PROPERTIES\t");
576       text.append(sg.getName());
577       text.append("\t");
578
579       if (sg.getDescription() != null)
580       {
581         text.append("description=");
582         text.append(sg.getDescription());
583         text.append("\t");
584       }
585       if (sg.cs != null)
586       {
587         text.append("colour=");
588         text.append(ColourSchemeProperty.getColourName(sg.cs));
589         text.append("\t");
590         if (sg.cs.getThreshold() != 0)
591         {
592           text.append("pidThreshold=");
593           text.append(sg.cs.getThreshold());
594         }
595         if (sg.cs.conservationApplied())
596         {
597           text.append("consThreshold=");
598           text.append(sg.cs.getConservationInc());
599           text.append("\t");
600         }
601       }
602       text.append("outlineColour=");
603       text.append(jalview.util.Format.getHexString(sg.getOutlineColour()));
604       text.append("\t");
605
606       text.append("displayBoxes=");
607       text.append(sg.getDisplayBoxes());
608       text.append("\t");
609       text.append("displayText=");
610       text.append(sg.getDisplayText());
611       text.append("\t");
612       text.append("colourText=");
613       text.append(sg.getColourText());
614       text.append("\t");
615       text.append("showUnconserved=");
616       text.append(sg.getShowNonconserved());
617       text.append("\t");
618       if (sg.textColour != java.awt.Color.black)
619       {
620         text.append("textCol1=");
621         text.append(jalview.util.Format.getHexString(sg.textColour));
622         text.append("\t");
623       }
624       if (sg.textColour2 != java.awt.Color.white)
625       {
626         text.append("textCol2=");
627         text.append(jalview.util.Format.getHexString(sg.textColour2));
628         text.append("\t");
629       }
630       if (sg.thresholdTextColour != 0)
631       {
632         text.append("textColThreshold=");
633         text.append(sg.thresholdTextColour);
634         text.append("\t");
635       }
636       if (sg.idColour != null)
637       {
638         text.append("idColour=");
639         text.append(jalview.util.Format.getHexString(sg.idColour));
640         text.append("\t");
641       }
642       if (sg.isHidereps())
643       {
644         text.append("hide=true\t");
645       }
646       if (sg.isHideCols())
647       {
648         text.append("hidecols=true\t");
649       }
650       if (seqrep != null)
651       {
652         // terminate the last line and clear the sequence ref for the group
653         text.append(newline);
654         text.append("SEQUENCE_REF");
655       }
656       text.append(newline);
657       text.append(newline);
658
659     }
660   }
661
662   SequenceI refSeq = null;
663
664   String refSeqId = null;
665
666   public boolean annotateAlignmentView(AlignViewportI viewport,
667           String file, String protocol)
668   {
669     ColumnSelection colSel = viewport.getColumnSelection();
670     if (colSel == null)
671     {
672       colSel = new ColumnSelection();
673     }
674     boolean rslt = readAnnotationFile(viewport.getAlignment(), colSel,
675             file, protocol);
676     if (rslt && (colSel.hasSelectedColumns() || colSel.hasHiddenColumns()))
677     {
678       viewport.setColumnSelection(colSel);
679     }
680
681     return rslt;
682   }
683
684   public boolean readAnnotationFile(AlignmentI al, String file,
685           String protocol)
686   {
687     return readAnnotationFile(al, null, file, protocol);
688   }
689
690   public boolean readAnnotationFile(AlignmentI al, ColumnSelection colSel,
691           String file, String protocol)
692   {
693     BufferedReader in = null;
694     try
695     {
696       if (protocol.equals(AppletFormatAdapter.FILE))
697       {
698         in = new BufferedReader(new FileReader(file));
699       }
700       else if (protocol.equals(AppletFormatAdapter.URL))
701       {
702         URL url = new URL(file);
703         in = new BufferedReader(new InputStreamReader(url.openStream()));
704       }
705       else if (protocol.equals(AppletFormatAdapter.PASTE))
706       {
707         in = new BufferedReader(new StringReader(file));
708       }
709       else if (protocol.equals(AppletFormatAdapter.CLASSLOADER))
710       {
711         java.io.InputStream is = getClass().getResourceAsStream("/" + file);
712         if (is != null)
713         {
714           in = new BufferedReader(new java.io.InputStreamReader(is));
715         }
716       }
717       if (in != null)
718       {
719         return parseAnnotationFrom(al, colSel, in);
720       }
721
722     } catch (Exception ex)
723     {
724       ex.printStackTrace();
725       System.out.println("Problem reading annotation file: " + ex);
726       if (nlinesread > 0)
727       {
728         System.out.println("Last read line " + nlinesread + ": '"
729                 + lastread + "' (first 80 chars) ...");
730       }
731       return false;
732     }
733     return false;
734   }
735
736   long nlinesread = 0;
737
738   String lastread = "";
739
740   private static String GRAPHLINE = "GRAPHLINE", COMBINE = "COMBINE",
741           STRUCTMODEL = "STRUCTMODEL";
742
743   public boolean parseAnnotationFrom(AlignmentI al, ColumnSelection colSel,
744           BufferedReader in) throws Exception
745   {
746     nlinesread = 0;
747     ArrayList<Object[]> combineAnnotation_calls = new ArrayList<Object[]>();
748     ArrayList<Object[]> deferredAnnotation_calls = new ArrayList<Object[]>();
749     boolean modified = false;
750     String groupRef = null;
751     Hashtable groupRefRows = new Hashtable();
752
753     Hashtable autoAnnots = new Hashtable();
754     {
755       String line, label, description, token;
756       int graphStyle, index;
757       int refSeqIndex = 1;
758       int existingAnnotations = 0;
759       // when true - will add new rows regardless of whether they are duplicate
760       // auto-annotation like consensus or conservation graphs
761       boolean overrideAutoAnnot = false;
762       if (al.getAlignmentAnnotation() != null)
763       {
764         existingAnnotations = al.getAlignmentAnnotation().length;
765         if (existingAnnotations > 0)
766         {
767           AlignmentAnnotation[] aa = al.getAlignmentAnnotation();
768           for (int aai = 0; aai < aa.length; aai++)
769           {
770             if (aa[aai].autoCalculated)
771             {
772               // make a note of the name and description
773               autoAnnots.put(
774                       autoAnnotsKey(aa[aai], aa[aai].sequenceRef,
775                               (aa[aai].groupRef == null ? null
776                                       : aa[aai].groupRef.getName())),
777                       new Integer(1));
778             }
779           }
780         }
781       }
782
783       int alWidth = al.getWidth();
784
785       StringTokenizer st;
786       Annotation[] annotations;
787       AlignmentAnnotation annotation = null;
788
789       // First confirm this is an Annotation file
790       boolean jvAnnotationFile = false;
791       while ((line = in.readLine()) != null)
792       {
793         nlinesread++;
794         lastread = new String(line);
795         if (line.indexOf("#") == 0)
796         {
797           continue;
798         }
799
800         if (line.indexOf("JALVIEW_ANNOTATION") > -1)
801         {
802           jvAnnotationFile = true;
803           break;
804         }
805       }
806
807       if (!jvAnnotationFile)
808       {
809         in.close();
810         return false;
811       }
812
813       while ((line = in.readLine()) != null)
814       {
815         nlinesread++;
816         lastread = new String(line);
817         if (line.indexOf("#") == 0
818                 || line.indexOf("JALVIEW_ANNOTATION") > -1
819                 || line.length() == 0)
820         {
821           continue;
822         }
823
824         st = new StringTokenizer(line, "\t");
825         token = st.nextToken();
826         if (token.equalsIgnoreCase("COLOUR"))
827         {
828           // TODO: use graduated colour def'n here too
829           colourAnnotations(al, st.nextToken(), st.nextToken());
830           modified = true;
831           continue;
832         }
833
834         else if (token.equalsIgnoreCase(COMBINE))
835         {
836           // keep a record of current state and resolve groupRef at end
837           combineAnnotation_calls
838                   .add(new Object[] { st, refSeq, groupRef });
839           modified = true;
840           continue;
841         }
842         else if (token.equalsIgnoreCase("ROWPROPERTIES"))
843         {
844           addRowProperties(al, st);
845           modified = true;
846           continue;
847         }
848         else if (token.equalsIgnoreCase(GRAPHLINE))
849         {
850           // resolve at end
851           deferredAnnotation_calls.add(new Object[] { GRAPHLINE, st,
852               refSeq, groupRef });
853           modified = true;
854           continue;
855         }
856
857         else if (token.equalsIgnoreCase("SEQUENCE_REF"))
858         {
859           if (st.hasMoreTokens())
860           {
861             refSeq = al.findName(refSeqId = st.nextToken());
862             if (refSeq == null)
863             {
864               refSeqId = null;
865             }
866             try
867             {
868               refSeqIndex = Integer.parseInt(st.nextToken());
869               if (refSeqIndex < 1)
870               {
871                 refSeqIndex = 1;
872                 System.out
873                         .println("WARNING: SEQUENCE_REF index must be > 0 in AnnotationFile");
874               }
875             } catch (Exception ex)
876             {
877               refSeqIndex = 1;
878             }
879           }
880           else
881           {
882             refSeq = null;
883             refSeqId = null;
884           }
885           continue;
886         }
887         else if (token.equalsIgnoreCase("GROUP_REF"))
888         {
889           // Group references could be forward or backwards, so they are
890           // resolved after the whole file is read in
891           groupRef = null;
892           if (st.hasMoreTokens())
893           {
894             groupRef = st.nextToken();
895             if (groupRef.length() < 1)
896             {
897               groupRef = null; // empty string
898             }
899             else
900             {
901               if (groupRefRows.get(groupRef) == null)
902               {
903                 groupRefRows.put(groupRef, new Vector());
904               }
905             }
906           }
907           continue;
908         }
909         else if (token.equalsIgnoreCase("SEQUENCE_GROUP"))
910         {
911           addGroup(al, st);
912           modified = true;
913           continue;
914         }
915
916         else if (token.equalsIgnoreCase("PROPERTIES"))
917         {
918           addProperties(al, st);
919           modified = true;
920           continue;
921         }
922
923         else if (token.equalsIgnoreCase("BELOW_ALIGNMENT"))
924         {
925           setBelowAlignment(al, st);
926           modified = true;
927           continue;
928         }
929         else if (token.equalsIgnoreCase("ALIGNMENT"))
930         {
931           addAlignmentDetails(al, st);
932           modified = true;
933           continue;
934         }
935         // else if (token.equalsIgnoreCase("VIEW_DEF"))
936         // {
937         // addOrSetView(al,st);
938         // modified = true;
939         // continue;
940         // }
941         else if (token.equalsIgnoreCase("VIEW_SETREF"))
942         {
943           if (refSeq != null)
944           {
945             al.setSeqrep(refSeq);
946           }
947           modified = true;
948           continue;
949         }
950         else if (token.equalsIgnoreCase("VIEW_HIDECOLS"))
951         {
952           if (st.hasMoreTokens())
953           {
954             if (colSel == null)
955             {
956               colSel = new ColumnSelection();
957             }
958             parseHideCols(colSel, st.nextToken());
959           }
960           modified = true;
961           continue;
962         }
963         else if (token.equalsIgnoreCase("HIDE_INSERTIONS"))
964         {
965           SequenceI sr = refSeq == null ? al.getSeqrep() : refSeq;
966           if (sr == null)
967           {
968             sr = al.getSequenceAt(0);
969           }
970           if (sr != null)
971           {
972             if (colSel == null)
973             {
974               System.err
975                       .println("Cannot process HIDE_INSERTIONS without an alignment view: Ignoring line: "
976                               + line);
977             }
978             else
979             {
980               // consider deferring this till after the file has been parsed ?
981               colSel.hideInsertionsFor(sr);
982             }
983           }
984           modified = true;
985           continue;
986         }
987         else if (token.equalsIgnoreCase(STRUCTMODEL))
988         {
989           boolean failedtoadd = true;
990           // expect
991           // STRUCTMODEL <QUERYID> <TemplateID> <URL to model> <URL to
992           // alignment>
993           if (st.hasMoreTokens()) {
994             refSeq = al.findName(refSeqId = st.nextToken());
995             if (refSeq == null)
996             {
997               System.err.println("Couldn't locate " + refSeqId
998                       + " in the alignment for STRUCTMODEL");
999               refSeqId = null;
1000             }
1001             else
1002             {
1003               String tempId = st.nextToken();
1004               String urlToModel = st.nextToken();
1005               String urlToPairwise = st.hasMoreTokens() ? st.nextToken()
1006                       : "";
1007               if (add_structmodel(al, refSeq, tempId, urlToModel,
1008                       urlToPairwise))
1009               {
1010                 failedtoadd = false;
1011               }
1012             }
1013           }
1014           if (failedtoadd)
1015           {
1016             System.err
1017                     .println("Need <QueryId> <TemplateId> <URL to Model> [<URL to pairwise alignment>] as tab separated fields after "
1018                             + STRUCTMODEL);
1019           } else {
1020             modified = true;
1021           }
1022           continue;
1023         }
1024         // Parse out the annotation row
1025         graphStyle = AlignmentAnnotation.getGraphValueFromString(token);
1026         label = st.nextToken();
1027
1028         index = 0;
1029         annotations = new Annotation[alWidth];
1030         description = null;
1031         float score = Float.NaN;
1032
1033         if (st.hasMoreTokens())
1034         {
1035           line = st.nextToken();
1036
1037           if (line.indexOf("|") == -1)
1038           {
1039             description = line;
1040             if (st.hasMoreTokens())
1041             {
1042               line = st.nextToken();
1043             }
1044           }
1045
1046           if (st.hasMoreTokens())
1047           {
1048             // This must be the score
1049             score = Float.valueOf(st.nextToken()).floatValue();
1050           }
1051
1052           st = new StringTokenizer(line, "|", true);
1053
1054           boolean emptyColumn = true;
1055           boolean onlyOneElement = (st.countTokens() == 1);
1056
1057           while (st.hasMoreElements() && index < alWidth)
1058           {
1059             token = st.nextToken().trim();
1060
1061             if (onlyOneElement)
1062             {
1063               try
1064               {
1065                 score = Float.valueOf(token).floatValue();
1066                 break;
1067               } catch (NumberFormatException ex)
1068               {
1069               }
1070             }
1071
1072             if (token.equals("|"))
1073             {
1074               if (emptyColumn)
1075               {
1076                 index++;
1077               }
1078
1079               emptyColumn = true;
1080             }
1081             else
1082             {
1083               annotations[index++] = parseAnnotation(token, graphStyle);
1084               emptyColumn = false;
1085             }
1086           }
1087
1088         }
1089
1090         annotation = new AlignmentAnnotation(label, description,
1091                 (index == 0) ? null : annotations, 0, 0, graphStyle);
1092
1093         annotation.score = score;
1094         if (!overrideAutoAnnot
1095                 && autoAnnots.containsKey(autoAnnotsKey(annotation, refSeq,
1096                         groupRef)))
1097         {
1098           // skip - we've already got an automatic annotation of this type.
1099           continue;
1100         }
1101         // otherwise add it!
1102         if (refSeq != null)
1103         {
1104
1105           annotation.belowAlignment = false;
1106           // make a copy of refSeq so we can find other matches in the alignment
1107           SequenceI referedSeq = refSeq;
1108           do
1109           {
1110             // copy before we do any mapping business.
1111             // TODO: verify that undo/redo with 1:many sequence associated
1112             // annotations can be undone correctly
1113             AlignmentAnnotation ann = new AlignmentAnnotation(annotation);
1114             annotation
1115                     .createSequenceMapping(referedSeq, refSeqIndex, false);
1116             annotation.adjustForAlignment();
1117             referedSeq.addAlignmentAnnotation(annotation);
1118             al.addAnnotation(annotation);
1119             al.setAnnotationIndex(annotation,
1120                     al.getAlignmentAnnotation().length
1121                             - existingAnnotations - 1);
1122             if (groupRef != null)
1123             {
1124               ((Vector) groupRefRows.get(groupRef)).addElement(annotation);
1125             }
1126             // and recover our virgin copy to use again if necessary.
1127             annotation = ann;
1128
1129           } while (refSeqId != null
1130                   && (referedSeq = al.findName(referedSeq, refSeqId, true)) != null);
1131         }
1132         else
1133         {
1134           al.addAnnotation(annotation);
1135           al.setAnnotationIndex(annotation,
1136                   al.getAlignmentAnnotation().length - existingAnnotations
1137                           - 1);
1138           if (groupRef != null)
1139           {
1140             ((Vector) groupRefRows.get(groupRef)).addElement(annotation);
1141           }
1142         }
1143         // and set modification flag
1144         modified = true;
1145       }
1146       // Resolve the groupRefs
1147       Hashtable<String, SequenceGroup> groupRefLookup = new Hashtable<String, SequenceGroup>();
1148       Enumeration en = groupRefRows.keys();
1149
1150       while (en.hasMoreElements())
1151       {
1152         groupRef = (String) en.nextElement();
1153         boolean matched = false;
1154         // Resolve group: TODO: add a getGroupByName method to alignments
1155         for (SequenceGroup theGroup : al.getGroups())
1156         {
1157           if (theGroup.getName().equals(groupRef))
1158           {
1159             if (matched)
1160             {
1161               // TODO: specify and implement duplication of alignment annotation
1162               // for multiple group references.
1163               System.err
1164                       .println("Ignoring 1:many group reference mappings for group name '"
1165                               + groupRef + "'");
1166             }
1167             else
1168             {
1169               matched = true;
1170               Vector rowset = (Vector) groupRefRows.get(groupRef);
1171               groupRefLookup.put(groupRef, theGroup);
1172               if (rowset != null && rowset.size() > 0)
1173               {
1174                 AlignmentAnnotation alan = null;
1175                 for (int elm = 0, elmSize = rowset.size(); elm < elmSize; elm++)
1176                 {
1177                   alan = (AlignmentAnnotation) rowset.elementAt(elm);
1178                   alan.groupRef = theGroup;
1179                 }
1180               }
1181             }
1182           }
1183         }
1184         ((Vector) groupRefRows.get(groupRef)).removeAllElements();
1185       }
1186       // process any deferred attribute settings for each context
1187       for (Object[] _deferred_args : deferredAnnotation_calls)
1188       {
1189         if (_deferred_args[0] == GRAPHLINE)
1190         {
1191           addLine(al,
1192                   (StringTokenizer) _deferred_args[1], // st
1193                   (SequenceI) _deferred_args[2], // refSeq
1194                   (_deferred_args[3] == null) ? null : groupRefLookup
1195                           .get(_deferred_args[3]) // the reference
1196                                                   // group, or null
1197           );
1198         }
1199       }
1200
1201       // finally, combine all the annotation rows within each context.
1202       /**
1203        * number of combine statements in this annotation file. Used to create
1204        * new groups for combined annotation graphs without disturbing existing
1205        * ones
1206        */
1207       int combinecount = 0;
1208       for (Object[] _combine_args : combineAnnotation_calls)
1209       {
1210         combineAnnotations(al,
1211                 ++combinecount,
1212                 (StringTokenizer) _combine_args[0], // st
1213                 (SequenceI) _combine_args[1], // refSeq
1214                 (_combine_args[2] == null) ? null : groupRefLookup
1215                         .get(_combine_args[2]) // the reference group,
1216                                                // or null
1217         );
1218       }
1219     }
1220     return modified;
1221   }
1222
1223   /**
1224    * resolve a structural model and generate and add an alignment sequence for
1225    * it
1226    * 
1227    * @param refSeq2
1228    * @param tempId
1229    * @param urlToModel
1230    * @param urlToPairwise
1231    * @return true if model and sequence was added
1232    */
1233   private boolean add_structmodel(AlignmentI al, SequenceI refSeq2, String tempId,
1234           String urlToModel, String urlToPairwise)
1235   {
1236     String warningMessage=null;
1237     boolean added = false;
1238     try {
1239       // locate tempId. if it exists, will need to merge, otherwise:
1240       SequenceI templateSeq = al.findName(tempId);
1241       // 1. load urlToModel
1242       // TODO: get base for current import operation and resolve against it
1243       // transfer to local temp file ?
1244       PDBEntry modelpe = new PDBEntry(tempId,null,Type.FILE,urlToModel);
1245       PDBEntry templpe = new PDBEntry(tempId, null, Type.FILE, urlToModel);
1246       refSeq2.addPDBId(modelpe);
1247       
1248       // 2. load urlToPairwise
1249       AlignmentI pwa = new AppletFormatAdapter().readFile(urlToPairwise,
1250               AppletFormatAdapter.checkProtocol(urlToPairwise), "FASTA");
1251       SequenceI qPw = null, tPw = null;
1252       if (pwa != null)
1253       {
1254         // resolve query/template sequences in provided alignment
1255         qPw = pwa.findName(refSeqId);
1256         tPw = pwa.findName(tempId);
1257       }
1258       if (false)
1259       // (qPw != null && tPw != null)
1260       {
1261         // not yet complete
1262         // refalQ vvva--addrvvvtttddd
1263         // refalT ---aaaa---sss---ddd
1264         // profalQ ---v-v-v-a.-.-a---dd--r--vvvtt--td--dd
1265         // profalT ---.-.-.-aa-a-a---..--.--sss..--.d--dd
1266         // Pragmatic solution here:
1267         // Map templpe onto refalT only where refalT and refalQ are both
1268         // non-gaps
1269
1270         // columns for start..end in refSeq2
1271         int[] gapMap = refSeq2.gapMap();
1272         // insert gaps in tPw
1273         int curi = 0, width = refSeq2.getLength();
1274         // TBC
1275       }
1276       else
1277       {
1278         // assume 1:1 - so synthesise sequences to use to construct mapping ?
1279         refSeq2.getDatasetSequence().addPDBId(modelpe);
1280         if (templateSeq == null && tPw != null)
1281         {
1282           tPw.createDatasetSequence();
1283           tPw.getDatasetSequence().addPDBId(templpe); // needs to set mapping based on model yet...
1284           al.addSequence(tPw);
1285           added = true;
1286         }
1287       }
1288     // 3. pad/insert gaps in urlToPairwise according to gaps already present in
1289     // refSeq2
1290     // 4. add padded tempId sequence to alignment
1291     // 4. associate urlToModel with refSeq2 based on position map provided by
1292     // urlToPairwise
1293     // 5. associate urlToModel with tempId based on position map provided by
1294     // urlToPairwise
1295     // start a thread to load urlToModel and process/annotate sequences.
1296     } catch (IOException x)
1297     {
1298       warningMessage = x.toString();
1299     } finally {
1300       if (warningMessage !=null)
1301       {
1302         System.err.println("Warnings whilst processing STRUCTMODEL: "+warningMessage);
1303       }
1304       return added;
1305     }
1306   }
1307
1308   private void parseHideCols(ColumnSelection colSel, String nextToken)
1309   {
1310     StringTokenizer inval = new StringTokenizer(nextToken, ",");
1311     while (inval.hasMoreTokens())
1312     {
1313       String range = inval.nextToken().trim();
1314       int from, to = range.indexOf("-");
1315       if (to == -1)
1316       {
1317         from = to = Integer.parseInt(range);
1318         if (from >= 0)
1319         {
1320           colSel.hideColumns(from, to);
1321         }
1322       }
1323       else
1324       {
1325         from = Integer.parseInt(range.substring(0, to));
1326         if (to < range.length() - 1)
1327         {
1328           to = Integer.parseInt(range.substring(to + 1));
1329         }
1330         else
1331         {
1332           to = from;
1333         }
1334         if (from > 0 && to >= from)
1335         {
1336           colSel.hideColumns(from, to);
1337         }
1338       }
1339     }
1340   }
1341
1342   private Object autoAnnotsKey(AlignmentAnnotation annotation,
1343           SequenceI refSeq, String groupRef)
1344   {
1345     return annotation.graph + "\t" + annotation.label + "\t"
1346             + annotation.description + "\t"
1347             + (refSeq != null ? refSeq.getDisplayId(true) : "");
1348   }
1349
1350   Annotation parseAnnotation(String string, int graphStyle)
1351   {
1352     boolean hasSymbols = (graphStyle == AlignmentAnnotation.NO_GRAPH); // don't
1353     // do the
1354     // glyph
1355     // test
1356     // if we
1357     // don't
1358     // want
1359     // secondary
1360     // structure
1361     String desc = null, displayChar = null;
1362     char ss = ' '; // secondaryStructure
1363     float value = 0;
1364     boolean parsedValue = false, dcset = false;
1365
1366     // find colour here
1367     java.awt.Color colour = null;
1368     int i = string.indexOf("[");
1369     int j = string.indexOf("]");
1370     if (i > -1 && j > -1)
1371     {
1372       UserColourScheme ucs = new UserColourScheme();
1373
1374       colour = ucs.getColourFromString(string.substring(i + 1, j));
1375       if (i > 0 && string.charAt(i - 1) == ',')
1376       {
1377         // clip the preceding comma as well
1378         i--;
1379       }
1380       string = string.substring(0, i) + string.substring(j + 1);
1381     }
1382
1383     StringTokenizer st = new StringTokenizer(string, ",", true);
1384     String token;
1385     boolean seenContent = false;
1386     int pass = 0;
1387     while (st.hasMoreTokens())
1388     {
1389       pass++;
1390       token = st.nextToken().trim();
1391       if (token.equals(","))
1392       {
1393         if (!seenContent && parsedValue && !dcset)
1394         {
1395           // allow the value below the bar/line to be empty
1396           dcset = true;
1397           displayChar = " ";
1398         }
1399         seenContent = false;
1400         continue;
1401       }
1402       else
1403       {
1404         seenContent = true;
1405       }
1406
1407       if (!parsedValue)
1408       {
1409         try
1410         {
1411           displayChar = token;
1412           // foo
1413           value = new Float(token).floatValue();
1414           parsedValue = true;
1415           continue;
1416         } catch (NumberFormatException ex)
1417         {
1418         }
1419       }
1420       else
1421       {
1422         if (token.length() == 1)
1423         {
1424           displayChar = token;
1425         }
1426       }
1427       if (hasSymbols
1428               && (token.length() == 1 && "()<>[]{}AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
1429                       .contains(token)))
1430       {
1431         // Either this character represents a helix or sheet
1432         // or an integer which can be displayed
1433         ss = token.charAt(0);
1434         if (displayChar.equals(token.substring(0, 1)))
1435         {
1436           displayChar = "";
1437         }
1438       }
1439       else if (desc == null || (parsedValue && pass > 2))
1440       {
1441         desc = token;
1442       }
1443
1444     }
1445     // if (!dcset && string.charAt(string.length() - 1) == ',')
1446     // {
1447     // displayChar = " "; // empty display char symbol.
1448     // }
1449     if (displayChar != null && desc != null && desc.length() == 1)
1450     {
1451       if (displayChar.length() > 1)
1452       {
1453         // switch desc and displayChar - legacy support
1454         String tmp = displayChar;
1455         displayChar = desc;
1456         desc = tmp;
1457       }
1458       else
1459       {
1460         if (displayChar.equals(desc))
1461         {
1462           // duplicate label - hangover from the 'robust parser' above
1463           desc = null;
1464         }
1465       }
1466     }
1467     Annotation anot = new Annotation(displayChar, desc, ss, value);
1468
1469     anot.colour = colour;
1470
1471     return anot;
1472   }
1473
1474   void colourAnnotations(AlignmentI al, String label, String colour)
1475   {
1476     UserColourScheme ucs = new UserColourScheme(colour);
1477     Annotation[] annotations;
1478     for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1479     {
1480       if (al.getAlignmentAnnotation()[i].label.equalsIgnoreCase(label))
1481       {
1482         annotations = al.getAlignmentAnnotation()[i].annotations;
1483         for (int j = 0; j < annotations.length; j++)
1484         {
1485           if (annotations[j] != null)
1486           {
1487             annotations[j].colour = ucs.findColour('A');
1488           }
1489         }
1490       }
1491     }
1492   }
1493
1494   void combineAnnotations(AlignmentI al, int combineCount,
1495           StringTokenizer st, SequenceI seqRef, SequenceGroup groupRef)
1496   {
1497     String group = st.nextToken();
1498     // First make sure we are not overwriting the graphIndex
1499     int graphGroup = 0;
1500     if (al.getAlignmentAnnotation() != null)
1501     {
1502       for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1503       {
1504         AlignmentAnnotation aa = al.getAlignmentAnnotation()[i];
1505
1506         if (aa.graphGroup > graphGroup)
1507         {
1508           // try to number graphGroups in order of occurence.
1509           graphGroup = aa.graphGroup + 1;
1510         }
1511         if (aa.sequenceRef == seqRef && aa.groupRef == groupRef
1512                 && aa.label.equalsIgnoreCase(group))
1513         {
1514           if (aa.graphGroup > -1)
1515           {
1516             graphGroup = aa.graphGroup;
1517           }
1518           else
1519           {
1520             if (graphGroup <= combineCount)
1521             {
1522               graphGroup = combineCount + 1;
1523             }
1524             aa.graphGroup = graphGroup;
1525           }
1526           break;
1527         }
1528       }
1529
1530       // Now update groups
1531       while (st.hasMoreTokens())
1532       {
1533         group = st.nextToken();
1534         for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1535         {
1536           AlignmentAnnotation aa = al.getAlignmentAnnotation()[i];
1537           if (aa.sequenceRef == seqRef && aa.groupRef == groupRef
1538                   && aa.label.equalsIgnoreCase(group))
1539           {
1540             aa.graphGroup = graphGroup;
1541             break;
1542           }
1543         }
1544       }
1545     }
1546     else
1547     {
1548       System.err
1549               .println("Couldn't combine annotations. None are added to alignment yet!");
1550     }
1551   }
1552
1553   void addLine(AlignmentI al, StringTokenizer st, SequenceI seqRef,
1554           SequenceGroup groupRef)
1555   {
1556     String group = st.nextToken();
1557     AlignmentAnnotation annotation = null, alannot[] = al
1558             .getAlignmentAnnotation();
1559     float value = new Float(st.nextToken()).floatValue();
1560     String label = st.hasMoreTokens() ? st.nextToken() : null;
1561     java.awt.Color colour = null;
1562     if (st.hasMoreTokens())
1563     {
1564       UserColourScheme ucs = new UserColourScheme(st.nextToken());
1565       colour = ucs.findColour('A');
1566     }
1567     if (alannot != null)
1568     {
1569       for (int i = 0; i < alannot.length; i++)
1570       {
1571         if (alannot[i].label.equalsIgnoreCase(group)
1572                 && (seqRef == null || alannot[i].sequenceRef == seqRef)
1573                 && (groupRef == null || alannot[i].groupRef == groupRef))
1574         {
1575           alannot[i].setThreshold(new GraphLine(value, label, colour));
1576         }
1577       }
1578     }
1579     if (annotation == null)
1580     {
1581       return;
1582     }
1583   }
1584
1585   void addGroup(AlignmentI al, StringTokenizer st)
1586   {
1587     SequenceGroup sg = new SequenceGroup();
1588     sg.setName(st.nextToken());
1589     String rng = "";
1590     try
1591     {
1592       rng = st.nextToken();
1593       if (rng.length() > 0 && !rng.startsWith("*"))
1594       {
1595         sg.setStartRes(Integer.parseInt(rng) - 1);
1596       }
1597       else
1598       {
1599         sg.setStartRes(0);
1600       }
1601       rng = st.nextToken();
1602       if (rng.length() > 0 && !rng.startsWith("*"))
1603       {
1604         sg.setEndRes(Integer.parseInt(rng) - 1);
1605       }
1606       else
1607       {
1608         sg.setEndRes(al.getWidth() - 1);
1609       }
1610     } catch (Exception e)
1611     {
1612       System.err
1613               .println("Couldn't parse Group Start or End Field as '*' or a valid column or sequence index: '"
1614                       + rng + "' - assuming alignment width for group.");
1615       // assume group is full width
1616       sg.setStartRes(0);
1617       sg.setEndRes(al.getWidth() - 1);
1618     }
1619
1620     String index = st.nextToken();
1621     if (index.equals("-1"))
1622     {
1623       while (st.hasMoreElements())
1624       {
1625         sg.addSequence(al.findName(st.nextToken()), false);
1626       }
1627     }
1628     else
1629     {
1630       StringTokenizer st2 = new StringTokenizer(index, ",");
1631
1632       while (st2.hasMoreTokens())
1633       {
1634         String tmp = st2.nextToken();
1635         if (tmp.equals("*"))
1636         {
1637           for (int i = 0; i < al.getHeight(); i++)
1638           {
1639             sg.addSequence(al.getSequenceAt(i), false);
1640           }
1641         }
1642         else if (tmp.indexOf("-") >= 0)
1643         {
1644           StringTokenizer st3 = new StringTokenizer(tmp, "-");
1645
1646           int start = (Integer.parseInt(st3.nextToken()));
1647           int end = (Integer.parseInt(st3.nextToken()));
1648
1649           if (end > start)
1650           {
1651             for (int i = start; i <= end; i++)
1652             {
1653               sg.addSequence(al.getSequenceAt(i - 1), false);
1654             }
1655           }
1656         }
1657         else
1658         {
1659           sg.addSequence(al.getSequenceAt(Integer.parseInt(tmp) - 1), false);
1660         }
1661       }
1662     }
1663
1664     if (refSeq != null)
1665     {
1666       sg.setStartRes(refSeq.findIndex(sg.getStartRes() + 1) - 1);
1667       sg.setEndRes(refSeq.findIndex(sg.getEndRes() + 1) - 1);
1668       sg.setSeqrep(refSeq);
1669     }
1670
1671     if (sg.getSize() > 0)
1672     {
1673       al.addGroup(sg);
1674     }
1675   }
1676
1677   void addRowProperties(AlignmentI al, StringTokenizer st)
1678   {
1679     String label = st.nextToken(), keyValue, key, value;
1680     boolean scaletofit = false, centerlab = false, showalllabs = false;
1681     while (st.hasMoreTokens())
1682     {
1683       keyValue = st.nextToken();
1684       key = keyValue.substring(0, keyValue.indexOf("="));
1685       value = keyValue.substring(keyValue.indexOf("=") + 1);
1686       if (key.equalsIgnoreCase("scaletofit"))
1687       {
1688         scaletofit = Boolean.valueOf(value).booleanValue();
1689       }
1690       if (key.equalsIgnoreCase("showalllabs"))
1691       {
1692         showalllabs = Boolean.valueOf(value).booleanValue();
1693       }
1694       if (key.equalsIgnoreCase("centrelabs"))
1695       {
1696         centerlab = Boolean.valueOf(value).booleanValue();
1697       }
1698       AlignmentAnnotation[] alr = al.getAlignmentAnnotation();
1699       if (alr != null)
1700       {
1701         for (int i = 0; i < alr.length; i++)
1702         {
1703           if (alr[i].label.equalsIgnoreCase(label))
1704           {
1705             alr[i].centreColLabels = centerlab;
1706             alr[i].scaleColLabel = scaletofit;
1707             alr[i].showAllColLabels = showalllabs;
1708           }
1709         }
1710       }
1711     }
1712   }
1713
1714   void addProperties(AlignmentI al, StringTokenizer st)
1715   {
1716
1717     // So far we have only added groups to the annotationHash,
1718     // the idea is in the future properties can be added to
1719     // alignments, other annotations etc
1720     if (al.getGroups() == null)
1721     {
1722       return;
1723     }
1724
1725     String name = st.nextToken();
1726     SequenceGroup sg = null;
1727     for (SequenceGroup _sg : al.getGroups())
1728     {
1729       if ((sg = _sg).getName().equals(name))
1730       {
1731         break;
1732       }
1733       else
1734       {
1735         sg = null;
1736       }
1737     }
1738
1739     if (sg != null)
1740     {
1741       String keyValue, key, value;
1742       ColourSchemeI def = sg.cs;
1743       sg.cs = null;
1744       while (st.hasMoreTokens())
1745       {
1746         keyValue = st.nextToken();
1747         key = keyValue.substring(0, keyValue.indexOf("="));
1748         value = keyValue.substring(keyValue.indexOf("=") + 1);
1749
1750         if (key.equalsIgnoreCase("description"))
1751         {
1752           sg.setDescription(value);
1753         }
1754         else if (key.equalsIgnoreCase("colour"))
1755         {
1756           sg.cs = ColourSchemeProperty.getColour(al, value);
1757         }
1758         else if (key.equalsIgnoreCase("pidThreshold"))
1759         {
1760           sg.cs.setThreshold(Integer.parseInt(value), true);
1761
1762         }
1763         else if (key.equalsIgnoreCase("consThreshold"))
1764         {
1765           sg.cs.setConservationInc(Integer.parseInt(value));
1766           Conservation c = new Conservation("Group",
1767                   ResidueProperties.propHash, 3, sg.getSequences(null),
1768                   sg.getStartRes(), sg.getEndRes() + 1);
1769
1770           c.calculate();
1771           c.verdict(false, 25); // TODO: refer to conservation percent threshold
1772
1773           sg.cs.setConservation(c);
1774
1775         }
1776         else if (key.equalsIgnoreCase("outlineColour"))
1777         {
1778           sg.setOutlineColour(new UserColourScheme(value).findColour('A'));
1779         }
1780         else if (key.equalsIgnoreCase("displayBoxes"))
1781         {
1782           sg.setDisplayBoxes(Boolean.valueOf(value).booleanValue());
1783         }
1784         else if (key.equalsIgnoreCase("showUnconserved"))
1785         {
1786           sg.setShowNonconserved(Boolean.valueOf(value).booleanValue());
1787         }
1788         else if (key.equalsIgnoreCase("displayText"))
1789         {
1790           sg.setDisplayText(Boolean.valueOf(value).booleanValue());
1791         }
1792         else if (key.equalsIgnoreCase("colourText"))
1793         {
1794           sg.setColourText(Boolean.valueOf(value).booleanValue());
1795         }
1796         else if (key.equalsIgnoreCase("textCol1"))
1797         {
1798           sg.textColour = new UserColourScheme(value).findColour('A');
1799         }
1800         else if (key.equalsIgnoreCase("textCol2"))
1801         {
1802           sg.textColour2 = new UserColourScheme(value).findColour('A');
1803         }
1804         else if (key.equalsIgnoreCase("textColThreshold"))
1805         {
1806           sg.thresholdTextColour = Integer.parseInt(value);
1807         }
1808         else if (key.equalsIgnoreCase("idColour"))
1809         {
1810           // consider warning if colour doesn't resolve to a real colour
1811           sg.setIdColour((def = new UserColourScheme(value))
1812                   .findColour('A'));
1813         }
1814         else if (key.equalsIgnoreCase("hide"))
1815         {
1816           // see bug https://mantis.lifesci.dundee.ac.uk/view.php?id=25847
1817           sg.setHidereps(true);
1818         }
1819         else if (key.equalsIgnoreCase("hidecols"))
1820         {
1821           // see bug https://mantis.lifesci.dundee.ac.uk/view.php?id=25847
1822           sg.setHideCols(true);
1823         }
1824         sg.recalcConservation();
1825       }
1826       if (sg.cs == null)
1827       {
1828         sg.cs = def;
1829       }
1830     }
1831   }
1832
1833   void setBelowAlignment(AlignmentI al, StringTokenizer st)
1834   {
1835     String token;
1836     AlignmentAnnotation aa, ala[] = al.getAlignmentAnnotation();
1837     if (ala == null)
1838     {
1839       System.err
1840               .print("Warning - no annotation to set below for sequence associated annotation:");
1841     }
1842     while (st.hasMoreTokens())
1843     {
1844       token = st.nextToken();
1845       if (ala == null)
1846       {
1847         System.err.print(" " + token);
1848       }
1849       else
1850       {
1851         for (int i = 0; i < al.getAlignmentAnnotation().length; i++)
1852         {
1853           aa = al.getAlignmentAnnotation()[i];
1854           if (aa.sequenceRef == refSeq && aa.label.equals(token))
1855           {
1856             aa.belowAlignment = true;
1857           }
1858         }
1859       }
1860     }
1861     if (ala == null)
1862     {
1863       System.err.print("\n");
1864     }
1865   }
1866
1867   void addAlignmentDetails(AlignmentI al, StringTokenizer st)
1868   {
1869     String keyValue, key, value;
1870     while (st.hasMoreTokens())
1871     {
1872       keyValue = st.nextToken();
1873       key = keyValue.substring(0, keyValue.indexOf("="));
1874       value = keyValue.substring(keyValue.indexOf("=") + 1);
1875       al.setProperty(key, value);
1876     }
1877   }
1878
1879   /**
1880    * Write annotations as a CSV file of the form 'label, value, value, ...' for
1881    * each row.
1882    * 
1883    * @param annotations
1884    * @return CSV file as a string.
1885    */
1886   public String printCSVAnnotations(AlignmentAnnotation[] annotations)
1887   {
1888     if (annotations == null)
1889     {
1890       return "";
1891     }
1892     StringBuffer sp = new StringBuffer();
1893     for (int i = 0; i < annotations.length; i++)
1894     {
1895       String atos = annotations[i].toString();
1896       int p = 0;
1897       do
1898       {
1899         int cp = atos.indexOf("\n", p);
1900         sp.append(annotations[i].label);
1901         sp.append(",");
1902         if (cp > p)
1903         {
1904           sp.append(atos.substring(p, cp + 1));
1905         }
1906         else
1907         {
1908           sp.append(atos.substring(p));
1909           sp.append(newline);
1910         }
1911         p = cp + 1;
1912       } while (p > 0);
1913     }
1914     return sp.toString();
1915   }
1916
1917   public String printAnnotationsForView(AlignViewportI viewport)
1918   {
1919     return printAnnotations(viewport.isShowAnnotation() ? viewport
1920             .getAlignment().getAlignmentAnnotation() : null, viewport
1921             .getAlignment().getGroups(), viewport.getAlignment()
1922             .getProperties(), viewport.getColumnSelection(),
1923             viewport.getAlignment(), null);
1924   }
1925
1926   public String printAnnotationsForAlignment(AlignmentI al)
1927   {
1928     return printAnnotations(al.getAlignmentAnnotation(), al.getGroups(),
1929             al.getProperties(), null, al, null);
1930   }
1931 }