JAL-2629 update spikes/mungo to latest
[jalview.git] / src / jalview / datamodel / SequenceGroup.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.datamodel;
22
23 import jalview.analysis.AAFrequency;
24 import jalview.analysis.Conservation;
25 import jalview.renderer.ResidueShader;
26 import jalview.renderer.ResidueShaderI;
27 import jalview.schemes.ColourSchemeI;
28 import jalview.util.MessageManager;
29 import jalview.workers.InformationThread;
30
31 import java.awt.Color;
32 import java.beans.PropertyChangeListener;
33 import java.beans.PropertyChangeSupport;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.List;
37 import java.util.Map;
38
39 /**
40  * Collects a set contiguous ranges on a set of sequences
41  * 
42  * @author $author$
43  * @version $Revision$
44  */
45 public class SequenceGroup implements AnnotatedCollectionI
46 {
47   // TODO ideally this event notification functionality should be separated into
48   // a subclass of ViewportProperties similarly to ViewportRanges.
49   // Done here as a quick fix for JAL-2665
50   public static final String SEQ_GROUP_CHANGED = "Sequence group changed";
51
52   private String groupName;
53
54   private String description;
55
56   private AnnotatedCollectionI context;
57
58   private Conservation conservationData;
59
60   private ProfilesI consensusProfiles;
61
62   private ProfilesI hmmProfiles;
63
64   private boolean displayBoxes = true;
65
66   private boolean displayText = true;
67
68   private boolean colourText = false;
69
70   /*
71    * true if the group is defined as a group on the alignment, false if it is
72    * just a selection
73    */
74   private boolean isDefined;
75
76   /*
77    * after Olivier's non-conserved only character display
78    */
79   private boolean showNonconserved;
80
81   /*
82    * sequences in the group
83    */
84   private List<SequenceI> sequences = new ArrayList<>();
85
86   /*
87    * representative sequence for this group (if any)
88    */
89   private SequenceI seqrep;
90
91   private int width = -1;
92
93   /*
94    * colour scheme applied to group if any
95    */
96   public ResidueShaderI cs;
97
98   // start column (base 0)
99   private int startRes;
100
101   // end column (base 0)
102   private int endRes;
103
104   public Color outlineColour = Color.black;
105
106   public Color idColour;
107
108   public int thresholdTextColour;
109
110   public Color textColour = Color.black;
111
112   public Color textColour2 = Color.white;
113
114   /*
115    * properties for consensus annotation
116    */
117   private boolean ignoreGapsInConsensus = true;
118
119   private boolean showSequenceLogo;
120
121   private boolean normaliseSequenceLogo;
122
123   private boolean showConsensusHistogram;
124
125   /*
126    * properties for HMM information annotation
127    */
128   private boolean hmmIgnoreBelowBackground = true;
129
130   private boolean hmmUseInfoLetterHeight;
131
132   private boolean hmmShowSequenceLogo;
133
134   private boolean hmmNormaliseSequenceLogo;
135
136   private boolean hmmShowHistogram;
137
138   /*
139    * visibility of rows or represented rows covered by group
140    */
141   private boolean hidereps;
142
143   /*
144    * visibility of columns intersecting this group
145    */
146   private boolean hidecols;
147
148   private AlignmentAnnotation consensus;
149
150   private AlignmentAnnotation conservation;
151
152   private AlignmentAnnotation hmmInformation;
153
154   /**
155    * Constructor, assigning a generated default name of "JGroup:" with object
156    * hashcode appended
157    */
158   public SequenceGroup()
159   {
160     groupName = "JGroup:" + this.hashCode();
161     cs = new ResidueShader();
162   }
163
164   /**
165    * Creates a new SequenceGroup object.
166    * 
167    * @param sequences
168    * @param groupName
169    * @param scheme
170    * @param displayBoxes
171    * @param displayText
172    * @param colourText
173    * @param start
174    *          first column of group
175    * @param end
176    *          last column of group
177    */
178   public SequenceGroup(List<SequenceI> sequences, String groupName,
179           ColourSchemeI scheme, boolean displayBoxes, boolean displayText,
180           boolean colourText, int start, int end)
181   {
182     this();
183     this.sequences = sequences;
184     this.groupName = groupName;
185     this.displayBoxes = displayBoxes;
186     this.displayText = displayText;
187     this.colourText = colourText;
188     this.cs = new ResidueShader(scheme);
189     startRes = start;
190     endRes = end;
191     recalcConservation();
192   }
193
194   /**
195    * copy constructor
196    * 
197    * @param seqsel
198    */
199   public SequenceGroup(SequenceGroup seqsel)
200   {
201     this();
202     if (seqsel != null)
203     {
204       sequences = new ArrayList<>();
205       sequences.addAll(seqsel.sequences);
206       if (seqsel.groupName != null)
207       {
208         groupName = new String(seqsel.groupName);
209       }
210       displayBoxes = seqsel.displayBoxes;
211       displayText = seqsel.displayText;
212       colourText = seqsel.colourText;
213       startRes = seqsel.startRes;
214       endRes = seqsel.endRes;
215       cs = new ResidueShader((ResidueShader) seqsel.cs);
216       if (seqsel.description != null)
217       {
218         description = new String(seqsel.description);
219       }
220       hidecols = seqsel.hidecols;
221       hidereps = seqsel.hidereps;
222       showNonconserved = seqsel.showNonconserved;
223       showSequenceLogo = seqsel.showSequenceLogo;
224       normaliseSequenceLogo = seqsel.normaliseSequenceLogo;
225       showConsensusHistogram = seqsel.showConsensusHistogram;
226       hmmShowSequenceLogo = seqsel.hmmShowSequenceLogo;
227       hmmNormaliseSequenceLogo = seqsel.hmmNormaliseSequenceLogo;
228       hmmShowHistogram = seqsel.hmmShowHistogram;
229       idColour = seqsel.idColour;
230       outlineColour = seqsel.outlineColour;
231       seqrep = seqsel.seqrep;
232       textColour = seqsel.textColour;
233       textColour2 = seqsel.textColour2;
234       thresholdTextColour = seqsel.thresholdTextColour;
235       width = seqsel.width;
236       ignoreGapsInConsensus = seqsel.ignoreGapsInConsensus;
237       hmmIgnoreBelowBackground = seqsel.hmmIgnoreBelowBackground;
238       hmmUseInfoLetterHeight = seqsel.hmmUseInfoLetterHeight;
239       if (seqsel.conservationData != null)
240       {
241         recalcConservation(); // safer than
242         // aaFrequency = (Vector) seqsel.aaFrequency.clone(); // ??
243       }
244     }
245   }
246
247   protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
248           this);
249
250   public void addPropertyChangeListener(PropertyChangeListener listener)
251   {
252     changeSupport.addPropertyChangeListener(listener);
253   }
254
255   public void removePropertyChangeListener(PropertyChangeListener listener)
256   {
257     changeSupport.removePropertyChangeListener(listener);
258   }
259
260   public boolean isShowSequenceLogo()
261   {
262     return showSequenceLogo;
263   }
264
265   public SequenceI[] getSelectionAsNewSequences(AlignmentI align)
266   {
267     int iSize = sequences.size();
268     SequenceI[] seqs = new SequenceI[iSize];
269     SequenceI[] inorder = getSequencesInOrder(align);
270
271     for (int i = 0, ipos = 0; i < inorder.length; i++)
272     {
273       SequenceI seq = inorder[i];
274
275       seqs[ipos] = seq.getSubSequence(startRes, endRes + 1);
276       if (seqs[ipos] != null)
277       {
278         seqs[ipos].setDescription(seq.getDescription());
279         seqs[ipos].setDBRefs(seq.getDBRefs());
280         seqs[ipos].setSequenceFeatures(seq.getSequenceFeatures());
281         if (seq.getDatasetSequence() != null)
282         {
283           seqs[ipos].setDatasetSequence(seq.getDatasetSequence());
284         }
285
286         if (seq.getAnnotation() != null)
287         {
288           AlignmentAnnotation[] alann = align.getAlignmentAnnotation();
289           // Only copy annotation that is either a score or referenced by the
290           // alignment's annotation vector
291           for (int a = 0; a < seq.getAnnotation().length; a++)
292           {
293             AlignmentAnnotation tocopy = seq.getAnnotation()[a];
294             if (alann != null)
295             {
296               boolean found = false;
297               for (int pos = 0; pos < alann.length; pos++)
298               {
299                 if (alann[pos] == tocopy)
300                 {
301                   found = true;
302                   break;
303                 }
304               }
305               if (!found)
306               {
307                 continue;
308               }
309             }
310             AlignmentAnnotation newannot = new AlignmentAnnotation(
311                     seq.getAnnotation()[a]);
312             newannot.restrict(startRes, endRes);
313             newannot.setSequenceRef(seqs[ipos]);
314             newannot.adjustForAlignment();
315             seqs[ipos].addAlignmentAnnotation(newannot);
316           }
317         }
318         ipos++;
319       }
320       else
321       {
322         iSize--;
323       }
324     }
325     if (iSize != inorder.length)
326     {
327       SequenceI[] nseqs = new SequenceI[iSize];
328       System.arraycopy(seqs, 0, nseqs, 0, iSize);
329       seqs = nseqs;
330     }
331     return seqs;
332
333   }
334
335   /**
336    * If sequence ends in gaps, the end residue can be correctly calculated here
337    * 
338    * @param seq
339    *          SequenceI
340    * @return int
341    */
342   public int findEndRes(SequenceI seq)
343   {
344     int eres = 0;
345     char ch;
346
347     for (int j = 0; j < endRes + 1 && j < seq.getLength(); j++)
348     {
349       ch = seq.getCharAt(j);
350       if (!jalview.util.Comparison.isGap((ch)))
351       {
352         eres++;
353       }
354     }
355
356     if (eres > 0)
357     {
358       eres += seq.getStart() - 1;
359     }
360
361     return eres;
362   }
363
364   @Override
365   public List<SequenceI> getSequences()
366   {
367     return sequences;
368   }
369
370   @Override
371   public List<SequenceI> getSequences(
372           Map<SequenceI, SequenceCollectionI> hiddenReps)
373   {
374     if (hiddenReps == null)
375     {
376       // TODO: need a synchronizedCollection here ?
377       return sequences;
378     }
379     else
380     {
381       List<SequenceI> allSequences = new ArrayList<>();
382       for (SequenceI seq : sequences)
383       {
384         allSequences.add(seq);
385         if (hiddenReps.containsKey(seq))
386         {
387           SequenceCollectionI hsg = hiddenReps.get(seq);
388           for (SequenceI seq2 : hsg.getSequences())
389           {
390             if (seq2 != seq && !allSequences.contains(seq2))
391             {
392               allSequences.add(seq2);
393             }
394           }
395         }
396       }
397
398       return allSequences;
399     }
400   }
401
402   public SequenceI[] getSequencesAsArray(
403           Map<SequenceI, SequenceCollectionI> map)
404   {
405     List<SequenceI> tmp = getSequences(map);
406     if (tmp == null)
407     {
408       return null;
409     }
410     return tmp.toArray(new SequenceI[tmp.size()]);
411   }
412
413   /**
414    * DOCUMENT ME!
415    * 
416    * @param col
417    *          DOCUMENT ME!
418    * 
419    * @return DOCUMENT ME!
420    */
421   public boolean adjustForRemoveLeft(int col)
422   {
423     // return value is true if the group still exists
424     if (startRes >= col)
425     {
426       startRes = startRes - col;
427     }
428
429     if (endRes >= col)
430     {
431       endRes = endRes - col;
432
433       if (startRes > endRes)
434       {
435         startRes = 0;
436       }
437     }
438     else
439     {
440       // must delete this group!!
441       return false;
442     }
443
444     return true;
445   }
446
447   /**
448    * DOCUMENT ME!
449    * 
450    * @param col
451    *          DOCUMENT ME!
452    * 
453    * @return DOCUMENT ME!
454    */
455   public boolean adjustForRemoveRight(int col)
456   {
457     if (startRes > col)
458     {
459       // delete this group
460       return false;
461     }
462
463     if (endRes >= col)
464     {
465       endRes = col;
466     }
467
468     return true;
469   }
470
471   /**
472    * DOCUMENT ME!
473    * 
474    * @return DOCUMENT ME!
475    */
476   public String getName()
477   {
478     return groupName;
479   }
480
481   public String getDescription()
482   {
483     return description;
484   }
485
486   /**
487    * DOCUMENT ME!
488    * 
489    * @param name
490    *          DOCUMENT ME!
491    */
492   public void setName(String name)
493   {
494     groupName = name;
495     // TODO: URGENT: update dependent objects (annotation row)
496   }
497
498   public void setDescription(String desc)
499   {
500     description = desc;
501   }
502
503   /**
504    * DOCUMENT ME!
505    * 
506    * @return DOCUMENT ME!
507    */
508   public Conservation getConservation()
509   {
510     return conservationData;
511   }
512
513   /**
514    * DOCUMENT ME!
515    * 
516    * @param c
517    *          DOCUMENT ME!
518    */
519   public void setConservation(Conservation c)
520   {
521     conservationData = c;
522   }
523
524   /**
525    * Add s to this sequence group. If aligment sequence is already contained in
526    * group, it will not be added again, but recalculation may happen if the flag
527    * is set.
528    * 
529    * @param s
530    *          alignment sequence to be added
531    * @param recalc
532    *          true means Group's conservation should be recalculated
533    */
534   public void addSequence(SequenceI s, boolean recalc)
535   {
536     synchronized (sequences)
537     {
538       if (s != null && !sequences.contains(s))
539       {
540         sequences.add(s);
541         changeSupport.firePropertyChange(SEQ_GROUP_CHANGED,
542                 sequences.size() - 1, sequences.size());
543       }
544
545       if (recalc)
546       {
547         recalcConservation();
548       }
549     }
550   }
551
552   /**
553    * Max Gaps Threshold (percent) for performing a conservation calculation
554    */
555   private int consPercGaps = 25;
556
557   /**
558    * @return Max Gaps Threshold for performing a conservation calculation
559    */
560   public int getConsPercGaps()
561   {
562     return consPercGaps;
563   }
564
565   /**
566    * set Max Gaps Threshold (percent) for performing a conservation calculation
567    * 
568    * @param consPercGaps
569    */
570   public void setConsPercGaps(int consPercGaps)
571   {
572     this.consPercGaps = consPercGaps;
573   }
574
575   /**
576    * calculate residue conservation and colourschemes for group - but only if
577    * necessary. returns true if the calculation resulted in a visible change to
578    * group
579    */
580   public boolean recalcConservation()
581   {
582     return recalcAnnotations(false);
583   }
584
585   /**
586    * Recalculates column consensus, conservation, and HMM annotation for the
587    * group (as applicable). Returns true if the calculation resulted in a
588    * visible change to group.
589    * 
590    * @param defer
591    *          when set, colourschemes for this group are not refreshed after
592    *          recalculation
593    */
594   public boolean recalcAnnotations(boolean defer)
595   {
596     if (cs == null && consensus == null && conservation == null
597             && hmmInformation == null)
598     {
599       return false;
600     }
601     // TODO: try harder to detect changes in state in order to minimise
602     // recalculation effort
603     boolean upd = false;
604     try
605     {
606       ProfilesI cnsns = AAFrequency.calculate(sequences, startRes,
607               endRes + 1, showSequenceLogo);
608       if (hmmInformation != null)
609       {
610         HiddenMarkovModel hmm = hmmInformation.sequenceRef.getHMM();
611
612         ProfilesI info = AAFrequency.calculateHMMProfiles(hmm,
613                 (endRes + 1) - startRes, startRes, endRes + 1,
614                 hmmIgnoreBelowBackground, hmmUseInfoLetterHeight);
615         _updateInformationRow(info);
616         upd = true;
617       }
618       if (consensus != null)
619       {
620         _updateConsensusRow(cnsns, sequences.size());
621         upd = true;
622       }
623       if (cs != null)
624       {
625         cs.setConsensus(cnsns);
626         upd = true;
627       }
628
629       if ((conservation != null)
630               || (cs != null && cs.conservationApplied()))
631       {
632         Conservation c = new Conservation(groupName, sequences, startRes,
633                 endRes + 1);
634         c.calculate();
635         c.verdict(false, consPercGaps);
636         if (conservation != null)
637         {
638           _updateConservationRow(c);
639         }
640         if (cs != null)
641         {
642           if (cs.conservationApplied())
643           {
644             cs.setConservation(c);
645           }
646         }
647         // eager update - will cause a refresh of overview regardless
648         upd = true;
649       }
650       if (cs != null && !defer)
651       {
652         // TODO: JAL-2034 should cs.alignmentChanged modify return state
653         cs.alignmentChanged(context != null ? context : this, null);
654         return true;
655       }
656       else
657       {
658         return upd;
659       }
660     } catch (java.lang.OutOfMemoryError err)
661     {
662       // TODO: catch OOM
663       System.out.println("Out of memory loading groups: " + err);
664     }
665     return upd;
666   }
667
668   private void _updateConservationRow(Conservation c)
669   {
670     if (conservation == null)
671     {
672       getConservation();
673     }
674     // update Labels
675     conservation.label = "Conservation for " + getName();
676     conservation.description = "Conservation for group " + getName()
677             + " less than " + consPercGaps + "% gaps";
678     // preserve width if already set
679     int aWidth = (conservation.annotations != null)
680             ? (endRes < conservation.annotations.length
681                     ? conservation.annotations.length
682                     : endRes + 1)
683             : endRes + 1;
684     conservation.annotations = null;
685     conservation.annotations = new Annotation[aWidth]; // should be alignment
686                                                        // width
687     c.completeAnnotations(conservation, null, startRes, endRes + 1);
688   }
689
690   private void _updateConsensusRow(ProfilesI cnsns, long nseq)
691   {
692     if (consensus == null)
693     {
694       getConsensus();
695     }
696     consensus.label = "Consensus for " + getName();
697     consensus.description = "Percent Identity";
698     consensusProfiles = cnsns;
699     // preserve width if already set
700     int aWidth = (consensus.annotations != null)
701             ? (endRes < consensus.annotations.length
702                     ? consensus.annotations.length
703                     : endRes + 1)
704             : endRes + 1;
705     consensus.annotations = null;
706     consensus.annotations = new Annotation[aWidth]; // should be alignment width
707
708     AAFrequency.completeConsensus(consensus, cnsns, startRes, endRes + 1,
709             ignoreGapsInConsensus, showSequenceLogo, nseq); // TODO: setting
710                                                             // container
711     // for
712     // ignoreGapsInConsensusCalculation);
713   }
714
715   /**
716    * Recalculates the information content on the HMM annotation
717    * 
718    * @param cnsns
719    */
720   private void _updateInformationRow(ProfilesI cnsns)
721   {
722     if (hmmInformation == null)
723     {
724       createInformationAnnotation();
725     }
726     hmmInformation.description = MessageManager
727             .getString("label.information_description");
728     setHmmProfiles(cnsns);
729     // preserve width if already set
730     int aWidth = (hmmInformation.annotations != null)
731             ? (endRes < hmmInformation.annotations.length
732                     ? hmmInformation.annotations.length : endRes + 1)
733             : endRes + 1;
734     hmmInformation.annotations = null;
735     hmmInformation.annotations = new Annotation[aWidth]; // should be alignment
736                                                       // width
737     hmmInformation.setCalcId(InformationThread.HMM_CALC_ID);
738     AAFrequency.completeInformation(hmmInformation, cnsns, startRes,
739             endRes + 1);
740   }
741
742   /**
743    * @param s
744    *          sequence to either add or remove from group
745    * @param recalc
746    *          flag passed to delete/addSequence to indicate if group properties
747    *          should be recalculated
748    */
749   public void addOrRemove(SequenceI s, boolean recalc)
750   {
751     synchronized (sequences)
752     {
753       if (sequences.contains(s))
754       {
755         deleteSequence(s, recalc);
756       }
757       else
758       {
759         addSequence(s, recalc);
760       }
761     }
762   }
763
764   /**
765    * remove
766    * 
767    * @param s
768    *          to be removed
769    * @param recalc
770    *          true means recalculate conservation
771    */
772   public void deleteSequence(SequenceI s, boolean recalc)
773   {
774     synchronized (sequences)
775     {
776       sequences.remove(s);
777       changeSupport.firePropertyChange(SEQ_GROUP_CHANGED,
778               sequences.size() + 1, sequences.size());
779
780       if (recalc)
781       {
782         recalcConservation();
783       }
784     }
785   }
786
787   /**
788    * 
789    * 
790    * @return the first column selected by this group. Runs from 0<=i<N_cols
791    */
792   @Override
793   public int getStartRes()
794   {
795     return startRes;
796   }
797
798   /**
799    * 
800    * @return the groups last selected column. Runs from 0<=i<N_cols
801    */
802   @Override
803   public int getEndRes()
804   {
805     return endRes;
806   }
807
808   /**
809    * Set the first column selected by this group. Runs from 0<=i<N_cols
810    * 
811    * @param i
812    */
813   public void setStartRes(int i)
814   {
815     int before = startRes;
816     startRes = i;
817     changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, startRes);
818   }
819
820   /**
821    * Set the groups last selected column. Runs from 0<=i<N_cols
822    * 
823    * @param i
824    */
825   public void setEndRes(int i)
826   {
827     int before = endRes;
828     endRes = i;
829     changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, endRes);
830   }
831
832   /**
833    * @return number of sequences in group
834    */
835   public int getSize()
836   {
837     return sequences.size();
838   }
839
840   /**
841    * @param i
842    * @return the ith sequence
843    */
844   public SequenceI getSequenceAt(int i)
845   {
846     return sequences.get(i);
847   }
848
849   /**
850    * @param state
851    *          colourText
852    */
853   public void setColourText(boolean state)
854   {
855     colourText = state;
856   }
857
858   /**
859    * DOCUMENT ME!
860    * 
861    * @return DOCUMENT ME!
862    */
863   public boolean getColourText()
864   {
865     return colourText;
866   }
867
868   /**
869    * DOCUMENT ME!
870    * 
871    * @param state
872    *          DOCUMENT ME!
873    */
874   public void setDisplayText(boolean state)
875   {
876     displayText = state;
877   }
878
879   /**
880    * DOCUMENT ME!
881    * 
882    * @return DOCUMENT ME!
883    */
884   public boolean getDisplayText()
885   {
886     return displayText;
887   }
888
889   /**
890    * DOCUMENT ME!
891    * 
892    * @param state
893    *          DOCUMENT ME!
894    */
895   public void setDisplayBoxes(boolean state)
896   {
897     displayBoxes = state;
898   }
899
900   /**
901    * DOCUMENT ME!
902    * 
903    * @return DOCUMENT ME!
904    */
905   public boolean getDisplayBoxes()
906   {
907     return displayBoxes;
908   }
909
910   /**
911    * computes the width of current set of sequences and returns it
912    * 
913    * @return DOCUMENT ME!
914    */
915   @Override
916   public int getWidth()
917   {
918     synchronized (sequences)
919     {
920       // MC This needs to get reset when characters are inserted and deleted
921       boolean first = true;
922       for (SequenceI seq : sequences)
923       {
924         if (first || seq.getLength() > width)
925         {
926           width = seq.getLength();
927           first = false;
928         }
929       }
930       return width;
931     }
932   }
933
934   /**
935    * DOCUMENT ME!
936    * 
937    * @param c
938    *          DOCUMENT ME!
939    */
940   public void setOutlineColour(Color c)
941   {
942     outlineColour = c;
943   }
944
945   /**
946    * DOCUMENT ME!
947    * 
948    * @return DOCUMENT ME!
949    */
950   public Color getOutlineColour()
951   {
952     return outlineColour;
953   }
954
955   /**
956    * 
957    * returns the sequences in the group ordered by the ordering given by al.
958    * this used to return an array with null entries regardless, new behaviour is
959    * below. TODO: verify that this does not affect use in applet or application
960    * 
961    * @param al
962    *          Alignment
963    * @return SequenceI[] intersection of sequences in group with al, ordered by
964    *         al, or null if group does not intersect with al
965    */
966   public SequenceI[] getSequencesInOrder(AlignmentI al)
967   {
968     return getSequencesInOrder(al, true);
969   }
970
971   /**
972    * return an array representing the intersection of the group with al,
973    * optionally returning an array the size of al.getHeight() where nulls mark
974    * the non-intersected sequences
975    * 
976    * @param al
977    * @param trim
978    * @return null or array
979    */
980   public SequenceI[] getSequencesInOrder(AlignmentI al, boolean trim)
981   {
982     synchronized (sequences)
983     {
984       int sSize = sequences.size();
985       int alHeight = al.getHeight();
986
987       SequenceI[] seqs = new SequenceI[(trim) ? sSize : alHeight];
988
989       int index = 0;
990       for (int i = 0; i < alHeight && index < sSize; i++)
991       {
992         if (sequences.contains(al.getSequenceAt(i)))
993         {
994           seqs[(trim) ? index : i] = al.getSequenceAt(i);
995           index++;
996         }
997       }
998       if (index == 0)
999       {
1000         return null;
1001       }
1002       if (!trim)
1003       {
1004         return seqs;
1005       }
1006       if (index < seqs.length)
1007       {
1008         SequenceI[] dummy = seqs;
1009         seqs = new SequenceI[index];
1010         while (--index >= 0)
1011         {
1012           seqs[index] = dummy[index];
1013           dummy[index] = null;
1014         }
1015       }
1016       return seqs;
1017     }
1018   }
1019
1020   /**
1021    * @return the idColour
1022    */
1023   public Color getIdColour()
1024   {
1025     return idColour;
1026   }
1027
1028   /**
1029    * @param idColour
1030    *          the idColour to set
1031    */
1032   public void setIdColour(Color idColour)
1033   {
1034     this.idColour = idColour;
1035   }
1036
1037   /**
1038    * @return the representative sequence for this group
1039    */
1040   @Override
1041   public SequenceI getSeqrep()
1042   {
1043     return seqrep;
1044   }
1045
1046   /**
1047    * set the representative sequence for this group. Note - this affects the
1048    * interpretation of the Hidereps attribute.
1049    * 
1050    * @param seqrep
1051    *          the seqrep to set (null means no sequence representative)
1052    */
1053   @Override
1054   public void setSeqrep(SequenceI seqrep)
1055   {
1056     this.seqrep = seqrep;
1057   }
1058
1059   /**
1060    * 
1061    * @return true if group has a sequence representative
1062    */
1063   @Override
1064   public boolean hasSeqrep()
1065   {
1066     return seqrep != null;
1067   }
1068
1069   /**
1070    * set visibility of sequences covered by (if no sequence representative is
1071    * defined) or represented by this group.
1072    * 
1073    * @param visibility
1074    */
1075   public void setHidereps(boolean visibility)
1076   {
1077     hidereps = visibility;
1078   }
1079
1080   /**
1081    * 
1082    * @return true if sequences represented (or covered) by this group should be
1083    *         hidden
1084    */
1085   public boolean isHidereps()
1086   {
1087     return hidereps;
1088   }
1089
1090   /**
1091    * set intended visibility of columns covered by this group
1092    * 
1093    * @param visibility
1094    */
1095   public void setHideCols(boolean visibility)
1096   {
1097     hidecols = visibility;
1098   }
1099
1100   /**
1101    * 
1102    * @return true if columns covered by group should be hidden
1103    */
1104   public boolean isHideCols()
1105   {
1106     return hidecols;
1107   }
1108
1109   /**
1110    * create a new sequence group from the intersection of this group with an
1111    * alignment Hashtable of hidden representatives
1112    * 
1113    * @param alignment
1114    *          (may not be null)
1115    * @param map
1116    *          (may be null)
1117    * @return new group containing sequences common to this group and alignment
1118    */
1119   public SequenceGroup intersect(AlignmentI alignment,
1120           Map<SequenceI, SequenceCollectionI> map)
1121   {
1122     SequenceGroup sgroup = new SequenceGroup(this);
1123     SequenceI[] insect = getSequencesInOrder(alignment);
1124     sgroup.sequences = new ArrayList<>();
1125     for (int s = 0; insect != null && s < insect.length; s++)
1126     {
1127       if (map == null || map.containsKey(insect[s]))
1128       {
1129         sgroup.sequences.add(insect[s]);
1130       }
1131     }
1132     return sgroup;
1133   }
1134
1135   /**
1136    * @return the showUnconserved
1137    */
1138   public boolean getShowNonconserved()
1139   {
1140     return showNonconserved;
1141   }
1142
1143   /**
1144    * @param showNonconserved
1145    *          the showUnconserved to set
1146    */
1147   public void setShowNonconserved(boolean displayNonconserved)
1148   {
1149     this.showNonconserved = displayNonconserved;
1150   }
1151
1152   /**
1153    * set this alignmentAnnotation object as the one used to render consensus
1154    * annotation
1155    * 
1156    * @param aan
1157    */
1158   public void setConsensus(AlignmentAnnotation aan)
1159   {
1160     if (consensus == null)
1161     {
1162       consensus = aan;
1163     }
1164   }
1165
1166   /**
1167    * 
1168    * @return automatically calculated consensus row note: the row is a stub if a
1169    *         consensus calculation has not yet been performed on the group
1170    */
1171   public AlignmentAnnotation getConsensus()
1172   {
1173     // TODO get or calculate and get consensus annotation row for this group
1174     int aWidth = this.getWidth();
1175     // pointer
1176     // possibility
1177     // here.
1178     if (aWidth < 0)
1179     {
1180       return null;
1181     }
1182     if (consensus == null)
1183     {
1184       consensus = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1185               100f, AlignmentAnnotation.BAR_GRAPH);
1186       consensus.hasText = true;
1187       consensus.autoCalculated = true;
1188       consensus.groupRef = this;
1189       consensus.label = "Consensus for " + getName();
1190       consensus.description = "Percent Identity";
1191     }
1192     return consensus;
1193   }
1194
1195   /**
1196    * Creates the Hidden Markov Model annotation for this group
1197    */
1198   void createInformationAnnotation()
1199   {
1200     hmmInformation = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1201             6.25f, AlignmentAnnotation.BAR_GRAPH);
1202     hmmInformation.hasText = true;
1203     hmmInformation.autoCalculated = false;
1204     hmmInformation.groupRef = this;
1205     hmmInformation.label = getName();
1206     hmmInformation.description = MessageManager
1207             .getString("label.information_description");
1208     hmmInformation.setCalcId(InformationThread.HMM_CALC_ID);
1209   }
1210
1211   /**
1212    * set this alignmentAnnotation object as the one used to render consensus
1213    * annotation
1214    * 
1215    * @param aan
1216    */
1217   public void setConservationRow(AlignmentAnnotation aan)
1218   {
1219     if (conservation == null)
1220     {
1221       conservation = aan;
1222     }
1223   }
1224
1225   /**
1226    * get the conservation annotation row for this group
1227    * 
1228    * @return autoCalculated annotation row
1229    */
1230   public AlignmentAnnotation getConservationRow()
1231   {
1232     if (conservation == null)
1233     {
1234       conservation = new AlignmentAnnotation("", "", new Annotation[1], 0f,
1235               11f, AlignmentAnnotation.BAR_GRAPH);
1236     }
1237
1238     conservation.hasText = true;
1239     conservation.autoCalculated = true;
1240     conservation.groupRef = this;
1241     conservation.label = "Conservation for " + getName();
1242     conservation.description = "Conservation for group " + getName()
1243             + " less than " + consPercGaps + "% gaps";
1244     return conservation;
1245   }
1246
1247   /**
1248    * 
1249    * @return true if annotation rows have been instantiated for this group
1250    */
1251   public boolean hasAnnotationRows()
1252   {
1253     return consensus != null || conservation != null;
1254   }
1255
1256   public SequenceI getConsensusSeq()
1257   {
1258     getConsensus();
1259     StringBuffer seqs = new StringBuffer();
1260     for (int i = 0; i < consensus.annotations.length; i++)
1261     {
1262       if (consensus.annotations[i] != null)
1263       {
1264         String desc = consensus.annotations[i].description;
1265         if (desc.length() > 1 && desc.charAt(0) == '[')
1266         {
1267           seqs.append(desc.charAt(1));
1268         }
1269         else
1270         {
1271           seqs.append(consensus.annotations[i].displayCharacter);
1272         }
1273       }
1274     }
1275
1276     SequenceI sq = new Sequence("Group" + getName() + " Consensus",
1277             seqs.toString());
1278     sq.setDescription("Percentage Identity Consensus "
1279             + ((ignoreGapsInConsensus) ? " without gaps" : ""));
1280     return sq;
1281   }
1282
1283   public void setIgnoreGapsConsensus(boolean state)
1284   {
1285     if (this.ignoreGapsInConsensus != state && consensus != null)
1286     {
1287       ignoreGapsInConsensus = state;
1288       recalcConservation();
1289     }
1290     ignoreGapsInConsensus = state;
1291   }
1292
1293   public boolean isIgnoreGapsConsensus()
1294   {
1295     return ignoreGapsInConsensus;
1296   }
1297
1298   public void setIgnoreBelowBackground(boolean state)
1299   {
1300     hmmIgnoreBelowBackground = state;
1301   }
1302
1303   public boolean isIgnoreBelowBackground()
1304   {
1305     return hmmIgnoreBelowBackground;
1306   }
1307
1308   public void setInfoLetterHeight(boolean state)
1309   {
1310     hmmUseInfoLetterHeight = state;
1311   }
1312
1313   public boolean isUseInfoLetterHeight()
1314   {
1315     return hmmUseInfoLetterHeight;
1316   }
1317
1318   /**
1319    * @param showSequenceLogo
1320    *          indicates if a sequence logo is shown for consensus annotation
1321    */
1322   public void setshowSequenceLogo(boolean showSequenceLogo)
1323   {
1324     // TODO: decouple calculation from settings update
1325     if (this.showSequenceLogo != showSequenceLogo && consensus != null)
1326     {
1327       this.showSequenceLogo = showSequenceLogo;
1328       recalcConservation();
1329     }
1330     this.showSequenceLogo = showSequenceLogo;
1331   }
1332
1333   /**
1334    * 
1335    * @param showConsHist
1336    *          flag indicating if the consensus histogram for this group should
1337    *          be rendered
1338    */
1339   public void setShowConsensusHistogram(boolean showConsHist)
1340   {
1341
1342     if (showConsensusHistogram != showConsHist && consensus != null)
1343     {
1344       this.showConsensusHistogram = showConsHist;
1345       recalcConservation();
1346     }
1347     this.showConsensusHistogram = showConsHist;
1348   }
1349
1350   /**
1351    * @return the showConsensusHistogram
1352    */
1353   public boolean isShowConsensusHistogram()
1354   {
1355     return showConsensusHistogram;
1356   }
1357
1358   /**
1359    * set flag indicating if logo should be normalised when rendered
1360    * 
1361    * @param norm
1362    */
1363   public void setNormaliseSequenceLogo(boolean norm)
1364   {
1365     normaliseSequenceLogo = norm;
1366   }
1367
1368   public boolean isNormaliseSequenceLogo()
1369   {
1370     return normaliseSequenceLogo;
1371   }
1372
1373   @Override
1374   /**
1375    * returns a new array with all annotation involving this group
1376    */
1377   public AlignmentAnnotation[] getAlignmentAnnotation()
1378   {
1379     // TODO add in other methods like 'getAlignmentAnnotation(String label),
1380     // etc'
1381     ArrayList<AlignmentAnnotation> annot = new ArrayList<>();
1382     synchronized (sequences)
1383     {
1384       for (SequenceI seq : sequences)
1385       {
1386         AlignmentAnnotation[] aa = seq.getAnnotation();
1387         if (aa != null)
1388         {
1389           for (AlignmentAnnotation al : aa)
1390           {
1391             if (al.groupRef == this)
1392             {
1393               annot.add(al);
1394             }
1395           }
1396         }
1397       }
1398       if (consensus != null)
1399       {
1400         annot.add(consensus);
1401       }
1402       if (conservation != null)
1403       {
1404         annot.add(conservation);
1405       }
1406     }
1407     return annot.toArray(new AlignmentAnnotation[0]);
1408   }
1409
1410   @Override
1411   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1412   {
1413     return AlignmentAnnotation.findAnnotation(
1414             Arrays.asList(getAlignmentAnnotation()), calcId);
1415   }
1416
1417   @Override
1418   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
1419           String calcId, String label)
1420   {
1421     return AlignmentAnnotation.findAnnotations(
1422             Arrays.asList(getAlignmentAnnotation()), seq, calcId, label);
1423   }
1424
1425   /**
1426    * Answer true if any annotation matches the calcId passed in (if not null).
1427    * 
1428    * @param calcId
1429    * @return
1430    */
1431   public boolean hasAnnotation(String calcId)
1432   {
1433     return AlignmentAnnotation
1434             .hasAnnotation(Arrays.asList(getAlignmentAnnotation()), calcId);
1435   }
1436
1437   /**
1438    * Remove all sequences from the group (leaving other properties unchanged).
1439    */
1440   public void clear()
1441   {
1442     synchronized (sequences)
1443     {
1444       int before = sequences.size();
1445       sequences.clear();
1446       changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before,
1447               sequences.size());
1448     }
1449   }
1450
1451   /**
1452    * Sets the alignment or group context for this group, and whether it is
1453    * defined as a group
1454    * 
1455    * @param ctx
1456    *          the context for the group
1457    * @param defined
1458    *          whether the group is defined on the alignment or is just a
1459    *          selection
1460    * @throws IllegalArgumentException
1461    *           if setting the context would result in a circular reference chain
1462    */
1463   public void setContext(AnnotatedCollectionI ctx, boolean defined)
1464   {
1465     setContext(ctx);
1466     this.isDefined = defined;
1467   }
1468
1469   /**
1470    * Sets the alignment or group context for this group
1471    * 
1472    * @param ctx
1473    *          the context for the group
1474    * @throws IllegalArgumentException
1475    *           if setting the context would result in a circular reference chain
1476    */
1477   public void setContext(AnnotatedCollectionI ctx)
1478   {
1479     AnnotatedCollectionI ref = ctx;
1480     while (ref != null)
1481     {
1482       if (ref == this || ref.getContext() == ctx)
1483       {
1484         throw new IllegalArgumentException(
1485                 "Circular reference in SequenceGroup.context");
1486       }
1487       ref = ref.getContext();
1488     }
1489     this.context = ctx;
1490   }
1491
1492   /*
1493    * (non-Javadoc)
1494    * 
1495    * @see jalview.datamodel.AnnotatedCollectionI#getContext()
1496    */
1497   @Override
1498   public AnnotatedCollectionI getContext()
1499   {
1500     return context;
1501   }
1502
1503   public boolean isDefined()
1504   {
1505     return isDefined;
1506   }
1507
1508   public void setColourScheme(ColourSchemeI scheme)
1509   {
1510     if (cs == null)
1511     {
1512       cs = new ResidueShader();
1513     }
1514     cs.setColourScheme(scheme);
1515   }
1516
1517   public void setGroupColourScheme(ResidueShaderI scheme)
1518   {
1519     cs = scheme;
1520   }
1521
1522   public ColourSchemeI getColourScheme()
1523   {
1524     return cs == null ? null : cs.getColourScheme();
1525   }
1526
1527   public ResidueShaderI getGroupColourScheme()
1528   {
1529     return cs;
1530   }
1531
1532   @Override
1533   public boolean isNucleotide()
1534   {
1535     if (context != null)
1536     {
1537       return context.isNucleotide();
1538     }
1539     return false;
1540   }
1541
1542   /**
1543    * @param seq
1544    * @return true if seq is a member of the group
1545    */
1546
1547   public boolean contains(SequenceI seq1)
1548   {
1549     return sequences.contains(seq1);
1550   }
1551
1552   /**
1553    * @param seq
1554    * @param apos
1555    * @return true if startRes<=apos and endRes>=apos and seq is in the group
1556    */
1557   public boolean contains(SequenceI seq, int apos)
1558   {
1559     return (startRes <= apos && endRes >= apos) && sequences.contains(seq);
1560   }
1561
1562   public boolean isShowInformationHistogram()
1563   {
1564     return hmmShowHistogram;
1565   }
1566
1567   public void setShowInformationHistogram(boolean state)
1568   {
1569     if (hmmShowHistogram != state && hmmInformation != null)
1570     {
1571       this.hmmShowHistogram = state;
1572       // recalcConservation(); TODO don't know what to do here next
1573     }
1574     this.hmmShowHistogram = state;
1575   }
1576
1577   public boolean isShowHMMSequenceLogo()
1578   {
1579     return hmmShowSequenceLogo;
1580   }
1581
1582   public void setShowHMMSequenceLogo(boolean state)
1583   {
1584     hmmShowSequenceLogo = state;
1585   }
1586
1587   public boolean isNormaliseHMMSequenceLogo()
1588   {
1589     return hmmNormaliseSequenceLogo;
1590   }
1591
1592   public void setNormaliseHMMSequenceLogo(boolean state)
1593   {
1594     hmmNormaliseSequenceLogo = state;
1595   }
1596
1597   public ProfilesI getConsensusData()
1598   {
1599     return consensusProfiles;
1600   }
1601
1602   public ProfilesI getHmmProfiles()
1603   {
1604     return hmmProfiles;
1605   }
1606
1607   public void setHmmProfiles(ProfilesI hmmProfiles)
1608   {
1609     this.hmmProfiles = hmmProfiles;
1610   }
1611
1612   @Override
1613   public List<SequenceI> getHmmSequences()
1614   {
1615     List<SequenceI> result = new ArrayList<>();
1616     for (int i = 0; i < sequences.size(); i++)
1617     {
1618       SequenceI seq = sequences.get(i);
1619       if (seq.hasHMMProfile())
1620       {
1621         result.add(seq);
1622       }
1623     }
1624     return result;
1625   }
1626
1627 }