apply gpl development license
[jalview.git] / src / jalview / datamodel / SequenceGroup.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Development Version 2.4.1)
3  * Copyright (C) 2009 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.datamodel;
20
21 import java.util.*;
22
23 import java.awt.*;
24
25 import jalview.analysis.*;
26 import jalview.schemes.*;
27
28 /**
29  * Collects a set contiguous ranges on a set of sequences
30  * 
31  * @author $author$
32  * @version $Revision$
33  */
34 public class SequenceGroup
35 {
36   String groupName;
37
38   String description;
39
40   Conservation conserve;
41
42   Vector aaFrequency;
43
44   boolean displayBoxes = true;
45
46   boolean displayText = true;
47
48   boolean colourText = false;
49   /**
50    * after Olivier's non-conserved only character display
51    */
52   boolean showUnconserved = false;
53   
54   /**
55    * group members
56    */
57   private Vector sequences = new Vector();
58   /**
59    * representative sequence for this group (if any)
60    */
61   private SequenceI seqrep = null;
62   int width = -1;
63
64   /**
65    * Colourscheme applied to group if any */
66   public ColourSchemeI cs;
67
68   int startRes = 0;
69
70   int endRes = 0;
71
72   public Color outlineColour = Color.black;
73
74   public Color idColour = null;
75
76   public int thresholdTextColour = 0;
77
78   public Color textColour = Color.black;
79
80   public Color textColour2 = Color.white;
81
82   /**
83    * Creates a new SequenceGroup object.
84    */
85   public SequenceGroup()
86   {
87     groupName = "JGroup:" + this.hashCode();
88   }
89
90   /**
91    * Creates a new SequenceGroup object.
92    * 
93    * @param sequences
94    * @param groupName
95    * @param scheme
96    * @param displayBoxes
97    * @param displayText
98    * @param colourText
99    * @param start
100    *                first column of group
101    * @param end
102    *                last column of group
103    */
104   public SequenceGroup(Vector sequences, String groupName,
105           ColourSchemeI scheme, boolean displayBoxes, boolean displayText,
106           boolean colourText, int start, int end)
107   {
108     this.sequences = sequences;
109     this.groupName = groupName;
110     this.displayBoxes = displayBoxes;
111     this.displayText = displayText;
112     this.colourText = colourText;
113     this.cs = scheme;
114     startRes = start;
115     endRes = end;
116     recalcConservation();
117   }
118   /**
119    * copy constructor
120    * @param seqsel
121    */
122   public SequenceGroup(SequenceGroup seqsel)
123   {
124     if (seqsel!=null)
125     {
126       sequences=new Vector();
127       Enumeration sq = seqsel.sequences.elements();
128       while (sq.hasMoreElements()) { 
129         sequences.addElement(sq.nextElement()); 
130       };
131       if (seqsel.groupName!=null)
132       {
133         groupName = new String(seqsel.groupName);
134       }
135       displayBoxes = seqsel.displayBoxes;
136       displayText = seqsel.displayText;
137       colourText = seqsel.colourText;
138       startRes = seqsel.startRes;
139       endRes = seqsel.endRes;
140       cs =seqsel.cs;
141       if (seqsel.description!=null)
142         description = new String(seqsel.description);
143       hidecols = seqsel.hidecols;
144       hidereps = seqsel.hidereps;
145       idColour = seqsel.idColour;
146       outlineColour = seqsel.outlineColour;
147       seqrep = seqsel.seqrep;
148       textColour = seqsel.textColour;
149       textColour2 = seqsel.textColour2;
150       thresholdTextColour = seqsel.thresholdTextColour;
151       width = seqsel.width;
152       if (seqsel.conserve!=null)
153       {
154         recalcConservation(); // safer than 
155         // aaFrequency = (Vector) seqsel.aaFrequency.clone(); // ??
156       }
157     }
158   }
159
160   public SequenceI[] getSelectionAsNewSequences(AlignmentI align)
161   {
162     int iSize = sequences.size();
163     SequenceI[] seqs = new SequenceI[iSize];
164     SequenceI[] inorder = getSequencesInOrder(align);
165
166     for (int i = 0, ipos = 0; i < inorder.length; i++)
167     {
168       SequenceI seq = inorder[i];
169
170       seqs[ipos] = seq.getSubSequence(startRes, endRes + 1);
171       if (seqs[ipos] != null)
172       {
173         seqs[ipos].setDescription(seq.getDescription());
174         seqs[ipos].setDBRef(seq.getDBRef());
175         seqs[ipos].setSequenceFeatures(seq.getSequenceFeatures());
176         if (seq.getDatasetSequence() != null)
177         {
178           seqs[ipos].setDatasetSequence(seq.getDatasetSequence());
179         }
180
181         if (seq.getAnnotation() != null)
182         {
183           AlignmentAnnotation[] alann = align.getAlignmentAnnotation();
184           // Only copy annotation that is either a score or referenced by the
185           // alignment's annotation vector
186           for (int a = 0; a < seq.getAnnotation().length; a++)
187           {
188             AlignmentAnnotation tocopy = seq.getAnnotation()[a];
189             if (alann != null)
190             {
191               boolean found = false;
192               for (int pos = 0; pos < alann.length; pos++)
193               {
194                 if (alann[pos] == tocopy)
195                 {
196                   found = true;
197                   break;
198                 }
199               }
200               if (!found)
201                 continue;
202             }
203             AlignmentAnnotation newannot = new AlignmentAnnotation(seq
204                     .getAnnotation()[a]);
205             newannot.restrict(startRes, endRes);
206             newannot.setSequenceRef(seqs[ipos]);
207             newannot.adjustForAlignment();
208             seqs[ipos].addAlignmentAnnotation(newannot);
209           }
210         }
211         ipos++;
212       }
213       else
214       {
215         iSize--;
216       }
217     }
218     if (iSize != inorder.length)
219     {
220       SequenceI[] nseqs = new SequenceI[iSize];
221       System.arraycopy(seqs, 0, nseqs, 0, iSize);
222       seqs = nseqs;
223     }
224     return seqs;
225
226   }
227
228   /**
229    * If sequence ends in gaps, the end residue can be correctly calculated here
230    * 
231    * @param seq
232    *                SequenceI
233    * @return int
234    */
235   public int findEndRes(SequenceI seq)
236   {
237     int eres = 0;
238     char ch;
239
240     for (int j = 0; j < endRes + 1 && j < seq.getLength(); j++)
241     {
242       ch = seq.getCharAt(j);
243       if (!jalview.util.Comparison.isGap((ch)))
244       {
245         eres++;
246       }
247     }
248
249     if (eres > 0)
250     {
251       eres += seq.getStart() - 1;
252     }
253
254     return eres;
255   }
256
257   public Vector getSequences(Hashtable hiddenReps)
258   {
259     if (hiddenReps == null)
260     {
261       return sequences;
262     }
263     else
264     {
265       Vector allSequences = new Vector();
266       SequenceI seq, seq2;
267       for (int i = 0; i < sequences.size(); i++)
268       {
269         seq = (SequenceI) sequences.elementAt(i);
270         allSequences.addElement(seq);
271         if (hiddenReps.containsKey(seq))
272         {
273           SequenceGroup hsg = (SequenceGroup) hiddenReps.get(seq);
274           for (int h = 0; h < hsg.getSize(); h++)
275           {
276             seq2 = hsg.getSequenceAt(h);
277             if (seq2 != seq && !allSequences.contains(seq2))
278             {
279               allSequences.addElement(seq2);
280             }
281           }
282         }
283       }
284
285       return allSequences;
286     }
287   }
288
289   public SequenceI[] getSequencesAsArray(Hashtable hiddenReps)
290   {
291     Vector tmp = getSequences(hiddenReps);
292     if (tmp == null)
293     {
294       return null;
295     }
296     SequenceI[] result = new SequenceI[tmp.size()];
297     for (int i = 0; i < result.length; i++)
298     {
299       result[i] = (SequenceI) tmp.elementAt(i);
300     }
301
302     return result;
303   }
304
305   /**
306    * DOCUMENT ME!
307    * 
308    * @param col
309    *                DOCUMENT ME!
310    * 
311    * @return DOCUMENT ME!
312    */
313   public boolean adjustForRemoveLeft(int col)
314   {
315     // return value is true if the group still exists
316     if (startRes >= col)
317     {
318       startRes = startRes - col;
319     }
320
321     if (endRes >= col)
322     {
323       endRes = endRes - col;
324
325       if (startRes > endRes)
326       {
327         startRes = 0;
328       }
329     }
330     else
331     {
332       // must delete this group!!
333       return false;
334     }
335
336     return true;
337   }
338
339   /**
340    * DOCUMENT ME!
341    * 
342    * @param col
343    *                DOCUMENT ME!
344    * 
345    * @return DOCUMENT ME!
346    */
347   public boolean adjustForRemoveRight(int col)
348   {
349     if (startRes > col)
350     {
351       // delete this group
352       return false;
353     }
354
355     if (endRes >= col)
356     {
357       endRes = col;
358     }
359
360     return true;
361   }
362
363   /**
364    * DOCUMENT ME!
365    * 
366    * @return DOCUMENT ME!
367    */
368   public String getName()
369   {
370     return groupName;
371   }
372
373   public String getDescription()
374   {
375     return description;
376   }
377
378   /**
379    * DOCUMENT ME!
380    * 
381    * @param name
382    *                DOCUMENT ME!
383    */
384   public void setName(String name)
385   {
386     groupName = name;
387   }
388
389   public void setDescription(String desc)
390   {
391     description = desc;
392   }
393
394   /**
395    * DOCUMENT ME!
396    * 
397    * @return DOCUMENT ME!
398    */
399   public Conservation getConservation()
400   {
401     return conserve;
402   }
403
404   /**
405    * DOCUMENT ME!
406    * 
407    * @param c
408    *                DOCUMENT ME!
409    */
410   public void setConservation(Conservation c)
411   {
412     conserve = c;
413   }
414
415   /**
416    * Add s to this sequence group
417    * 
418    * @param s
419    *                alignment sequence to be added
420    * @param recalc
421    *                true means Group's conservation should be recalculated
422    */
423   public void addSequence(SequenceI s, boolean recalc)
424   {
425     if (s != null && !sequences.contains(s))
426     {
427       sequences.addElement(s);
428     }
429
430     if (recalc)
431     {
432       recalcConservation();
433     }
434   }
435
436   /**
437    * calculate residue conservation for group
438    */
439   public void recalcConservation()
440   {
441     if (cs == null)
442     {
443       return;
444     }
445
446     try
447     {
448       cs.setConsensus(AAFrequency
449               .calculate(sequences, startRes, endRes + 1));
450
451       if (cs instanceof ClustalxColourScheme)
452       {
453         ((ClustalxColourScheme) cs).resetClustalX(sequences, getWidth());
454       }
455
456       if (cs.conservationApplied())
457       {
458         Conservation c = new Conservation(groupName,
459                 ResidueProperties.propHash, 3, sequences, startRes,
460                 endRes + 1);
461         c.calculate();
462         c.verdict(false, 25);
463
464         cs.setConservation(c);
465
466         if (cs instanceof ClustalxColourScheme)
467         {
468           ((ClustalxColourScheme) cs).resetClustalX(sequences, getWidth());
469         }
470       }
471     } catch (java.lang.OutOfMemoryError err)
472     {
473       // TODO: catch OOM
474       System.out.println("Out of memory loading groups: " + err);
475     }
476
477   }
478
479   /**
480    * DOCUMENT ME!
481    * 
482    * @param s
483    *                DOCUMENT ME!
484    * @param recalc
485    *                DOCUMENT ME!
486    */
487   public void addOrRemove(SequenceI s, boolean recalc)
488   {
489     if (sequences.contains(s))
490     {
491       deleteSequence(s, recalc);
492     }
493     else
494     {
495       addSequence(s, recalc);
496     }
497   }
498
499   /**
500    * DOCUMENT ME!
501    * 
502    * @param s
503    *                DOCUMENT ME!
504    * @param recalc
505    *                DOCUMENT ME!
506    */
507   public void deleteSequence(SequenceI s, boolean recalc)
508   {
509     sequences.removeElement(s);
510
511     if (recalc)
512     {
513       recalcConservation();
514     }
515   }
516
517   /**
518    * DOCUMENT ME!
519    * 
520    * @return DOCUMENT ME!
521    */
522   public int getStartRes()
523   {
524     return startRes;
525   }
526
527   /**
528    * DOCUMENT ME!
529    * 
530    * @return DOCUMENT ME!
531    */
532   public int getEndRes()
533   {
534     return endRes;
535   }
536
537   /**
538    * DOCUMENT ME!
539    * 
540    * @param i
541    *                DOCUMENT ME!
542    */
543   public void setStartRes(int i)
544   {
545     startRes = i;
546   }
547
548   /**
549    * DOCUMENT ME!
550    * 
551    * @param i
552    *                DOCUMENT ME!
553    */
554   public void setEndRes(int i)
555   {
556     endRes = i;
557   }
558
559   /**
560    * DOCUMENT ME!
561    * 
562    * @return DOCUMENT ME!
563    */
564   public int getSize()
565   {
566     return sequences.size();
567   }
568
569   /**
570    * DOCUMENT ME!
571    * 
572    * @param i
573    *                DOCUMENT ME!
574    * 
575    * @return DOCUMENT ME!
576    */
577   public SequenceI getSequenceAt(int i)
578   {
579     return (SequenceI) sequences.elementAt(i);
580   }
581
582   /**
583    * DOCUMENT ME!
584    * 
585    * @param state
586    *                DOCUMENT ME!
587    */
588   public void setColourText(boolean state)
589   {
590     colourText = state;
591   }
592
593   /**
594    * DOCUMENT ME!
595    * 
596    * @return DOCUMENT ME!
597    */
598   public boolean getColourText()
599   {
600     return colourText;
601   }
602
603   /**
604    * DOCUMENT ME!
605    * 
606    * @param state
607    *                DOCUMENT ME!
608    */
609   public void setDisplayText(boolean state)
610   {
611     displayText = state;
612   }
613
614   /**
615    * DOCUMENT ME!
616    * 
617    * @return DOCUMENT ME!
618    */
619   public boolean getDisplayText()
620   {
621     return displayText;
622   }
623
624   /**
625    * DOCUMENT ME!
626    * 
627    * @param state
628    *                DOCUMENT ME!
629    */
630   public void setDisplayBoxes(boolean state)
631   {
632     displayBoxes = state;
633   }
634
635   /**
636    * DOCUMENT ME!
637    * 
638    * @return DOCUMENT ME!
639    */
640   public boolean getDisplayBoxes()
641   {
642     return displayBoxes;
643   }
644
645   /**
646    * DOCUMENT ME!
647    * 
648    * @return DOCUMENT ME!
649    */
650   public int getWidth()
651   {
652     // MC This needs to get reset when characters are inserted and deleted
653     if (sequences.size() > 0)
654     {
655       width = ((SequenceI) sequences.elementAt(0)).getLength();
656     }
657
658     for (int i = 1; i < sequences.size(); i++)
659     {
660       SequenceI seq = (SequenceI) sequences.elementAt(i);
661
662       if (seq.getLength() > width)
663       {
664         width = seq.getLength();
665       }
666     }
667
668     return width;
669   }
670
671   /**
672    * DOCUMENT ME!
673    * 
674    * @param c
675    *                DOCUMENT ME!
676    */
677   public void setOutlineColour(Color c)
678   {
679     outlineColour = c;
680   }
681
682   /**
683    * DOCUMENT ME!
684    * 
685    * @return DOCUMENT ME!
686    */
687   public Color getOutlineColour()
688   {
689     return outlineColour;
690   }
691
692   /**
693    * 
694    * returns the sequences in the group ordered by the ordering given by al.
695    * this used to return an array with null entries regardless, new behaviour is below.
696    * TODO: verify that this does not affect use in applet or application
697    * @param al
698    *                Alignment
699    * @return SequenceI[] intersection of sequences in group with al, ordered by al, or null if group does not intersect with al
700    */
701   public SequenceI[] getSequencesInOrder(AlignmentI al)
702   {
703     int sSize = sequences.size();
704     int alHeight = al.getHeight();
705
706     SequenceI[] seqs = new SequenceI[sSize];
707
708     int index = 0;
709     for (int i = 0; i < alHeight && index < sSize; i++)
710     {
711       if (sequences.contains(al.getSequenceAt(i)))
712       {
713         seqs[index++] = al.getSequenceAt(i);
714       }
715     }
716     if (index==0)
717     {
718       return null;
719     }
720     if (index<seqs.length)
721     {
722       SequenceI[] dummy = seqs;
723       seqs = new SequenceI[index];
724       while (--index>=0)
725       {
726         seqs[index] = dummy[index];
727         dummy[index] = null;
728       }
729     }
730     return seqs;
731   }
732
733   /**
734    * @return the idColour
735    */
736   public Color getIdColour()
737   {
738     return idColour;
739   }
740
741   /**
742    * @param idColour
743    *                the idColour to set
744    */
745   public void setIdColour(Color idColour)
746   {
747     this.idColour = idColour;
748   }
749
750   /**
751    * @return the representative sequence for this group
752    */
753   public SequenceI getSeqrep()
754   {
755     return seqrep;
756   }
757
758   /**
759    * set the representative sequence for this group.
760    * Note - this affects the interpretation of the Hidereps attribute.
761    * @param seqrep the seqrep to set (null means no sequence representative)
762    */
763   public void setSeqrep(SequenceI seqrep)
764   {
765     this.seqrep = seqrep;
766   }
767   /**
768    * 
769    * @return true if group has a sequence representative
770    */
771   public boolean hasSeqrep()
772   {
773     return seqrep != null;
774   }
775   /**
776    * visibility of rows or represented rows covered by group
777    */
778   private boolean hidereps=false;
779   /**
780    * set visibility of sequences covered by (if no sequence representative is defined) 
781    * or represented by this group.
782    * @param visibility
783    */
784   public void setHidereps(boolean visibility)
785   {
786     hidereps = visibility;
787   }
788   /**
789    * 
790    * @return true if sequences represented (or covered) by this group should be hidden
791    */
792   public boolean isHidereps()
793   {
794     return hidereps;
795   }
796   /**
797    * visibility of columns intersecting this group
798    */
799   private boolean hidecols=false;
800   /**
801    * set intended visibility of columns covered by this group
802    * @param visibility
803    */
804   public void setHideCols(boolean visibility)
805   {
806     hidecols = visibility;
807   }
808   /**
809    * 
810    * @return true if columns covered by group should be hidden
811    */
812   public boolean isHideCols()
813   {
814     return hidecols;
815   }
816   /**
817    * create a new sequence group from the intersection of this group
818    * with an alignment Hashtable of hidden representatives
819    * 
820    * @param alignment (may not be null)
821    * @param hashtable (may be null)
822    * @return new group containing sequences common to this group and alignment
823    */
824   public SequenceGroup intersect(AlignmentI alignment, Hashtable hashtable)
825   {
826     SequenceGroup sgroup = new SequenceGroup(this);
827     Enumeration en = getSequences(hashtable).elements();
828     while (en.hasMoreElements())
829     {
830       SequenceI elem = (SequenceI) en.nextElement();
831       if (alignment.getSequences().contains(elem))
832       {
833         sgroup.addSequence(elem, false);
834       }
835     }
836     return sgroup;
837   }
838
839   /**
840    * @return the showUnconserved
841    */
842   public boolean getShowunconserved()
843   {
844     return showUnconserved;
845   }
846
847   /**
848    * @param showUnconserved the showUnconserved to set
849    */
850   public void setShowunconserved(boolean displayNonconserved)
851   {
852     this.showUnconserved = displayNonconserved;
853   }
854
855   public AlignmentAnnotation getConsensus()
856   {
857     // TODO get or calculate and get consensus annotation row for this group
858     this.conserve.getConsSequence();
859     return null;
860   }
861 }