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