JAL-1115 refactor base collection for Alignment from Vector to locally synchronized...
[jalview.git] / src / jalview / analysis / AlignmentSorter.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.analysis;
19
20 import java.util.*;
21
22 import jalview.datamodel.*;
23 import jalview.util.*;
24
25 /**
26  * Routines for manipulating the order of a multiple sequence alignment TODO:
27  * this class retains some global states concerning sort-order which should be
28  * made attributes for the caller's alignment visualization. TODO: refactor to
29  * allow a subset of selected sequences to be sorted within the context of a
30  * whole alignment. Sort method template is: SequenceI[] tobesorted, [ input
31  * data mapping to each tobesorted element to use ], Alignment context of
32  * tobesorted that are to be re-ordered, boolean sortinplace, [special data - ie
33  * seuqence to be sorted w.r.t.]) sortinplace implies that the sorted vector
34  * resulting from applying the operation to tobesorted should be mapped back to
35  * the original positions in alignment. Otherwise, normal behaviour is to re
36  * order alignment so that tobesorted is sorted and grouped together starting
37  * from the first tobesorted position in the alignment. e.g. (a,tb2,b,tb1,c,tb3
38  * becomes a,tb1,tb2,tb3,b,c)
39  */
40 public class AlignmentSorter
41 {
42   /**
43    * todo: refactor searches to follow a basic pattern: (search property, last
44    * search state, current sort direction)
45    */
46   static boolean sortIdAscending = true;
47
48   static int lastGroupHash = 0;
49
50   static boolean sortGroupAscending = true;
51
52   static AlignmentOrder lastOrder = null;
53
54   static boolean sortOrderAscending = true;
55
56   static NJTree lastTree = null;
57
58   static boolean sortTreeAscending = true;
59
60   /**
61    * last Annotation Label used by sortByScore
62    */
63   private static String lastSortByScore;
64
65   private static boolean sortByScoreAscending = true;
66
67   /**
68    * compact representation of last arguments to SortByFeatureScore
69    */
70   private static String lastSortByFeatureScore;
71
72   private static boolean sortByFeatureScoreAscending = true;
73
74   private static boolean sortLengthAscending;
75
76   /**
77    * Sort by Percentage Identity w.r.t. s
78    * 
79    * @param align
80    *          AlignmentI
81    * @param s
82    *          SequenceI
83    * @param tosort
84    *          sequences from align that are to be sorted.
85    */
86   public static void sortByPID(AlignmentI align, SequenceI s,
87           SequenceI[] tosort)
88   {
89     sortByPID(align, s, tosort, 0, -1);
90   }
91
92   /**
93    * Sort by Percentage Identity w.r.t. s
94    * 
95    * @param align
96    *          AlignmentI
97    * @param s
98    *          SequenceI
99    * @param tosort
100    *          sequences from align that are to be sorted.
101    * @param start
102    *          start column (0 for beginning
103    * @param end
104    */
105   public static void sortByPID(AlignmentI align, SequenceI s,
106           SequenceI[] tosort, int start, int end)
107   {
108     int nSeq = align.getHeight();
109
110     float[] scores = new float[nSeq];
111     SequenceI[] seqs = new SequenceI[nSeq];
112
113     for (int i = 0; i < nSeq; i++)
114     {
115       scores[i] = Comparison.PID(align.getSequenceAt(i)
116               .getSequenceAsString(), s.getSequenceAsString());
117       seqs[i] = align.getSequenceAt(i);
118     }
119
120     QuickSort.sort(scores, 0, scores.length - 1, seqs);
121
122     setReverseOrder(align, seqs);
123   }
124
125   /**
126    * Reverse the order of the sort
127    * 
128    * @param align
129    *          DOCUMENT ME!
130    * @param seqs
131    *          DOCUMENT ME!
132    */
133   private static void setReverseOrder(AlignmentI align, SequenceI[] seqs)
134   {
135     int nSeq = seqs.length;
136
137     int len = 0;
138
139     if ((nSeq % 2) == 0)
140     {
141       len = nSeq / 2;
142     }
143     else
144     {
145       len = (nSeq + 1) / 2;
146     }
147
148     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
149     List<SequenceI> asq;
150     synchronized (asq = align.getSequences())
151     {
152       for (int i = 0; i < len; i++)
153       {
154         // SequenceI tmp = seqs[i];
155         asq.set(i, seqs[nSeq - i - 1]);
156         asq.set(nSeq - i - 1, seqs[i]);
157       }
158     }
159   }
160
161   /**
162    * Sets the Alignment object with the given sequences
163    * 
164    * @param align
165    *          Alignment object to be updated
166    * @param tmp
167    *          sequences as a vector
168    */
169   private static void setOrder(AlignmentI align, Vector tmp)
170   {
171     setOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
172   }
173
174   /**
175    * Sets the Alignment object with the given sequences
176    * 
177    * @param align
178    *          DOCUMENT ME!
179    * @param seqs
180    *          sequences as an array
181    */
182   public static void setOrder(AlignmentI align, SequenceI[] seqs)
183   {
184     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
185     List<SequenceI> algn;
186     synchronized (algn = align.getSequences())
187     {
188       List<SequenceI> tmp = new ArrayList<SequenceI>();
189
190       for (int i = 0; i < seqs.length; i++)
191       {
192         if (algn.contains(seqs[i]))
193         {
194           tmp.add(seqs[i]);
195         }
196       }
197
198       algn.clear();
199       // User may have hidden seqs, then clicked undo or redo
200       for (int i = 0; i < tmp.size(); i++)
201       {
202         algn.add(tmp.get(i));
203       }
204     }
205   }
206
207   /**
208    * Sorts by ID. Numbers are sorted before letters.
209    * 
210    * @param align
211    *          The alignment object to sort
212    */
213   public static void sortByID(AlignmentI align)
214   {
215     int nSeq = align.getHeight();
216
217     String[] ids = new String[nSeq];
218     SequenceI[] seqs = new SequenceI[nSeq];
219
220     for (int i = 0; i < nSeq; i++)
221     {
222       ids[i] = align.getSequenceAt(i).getName();
223       seqs[i] = align.getSequenceAt(i);
224     }
225
226     QuickSort.sort(ids, seqs);
227
228     if (sortIdAscending)
229     {
230       setReverseOrder(align, seqs);
231     }
232     else
233     {
234       setOrder(align, seqs);
235     }
236
237     sortIdAscending = !sortIdAscending;
238   }
239
240   /**
241    * Sorts by sequence length
242    * 
243    * @param align
244    *          The alignment object to sort
245    */
246   public static void sortByLength(AlignmentI align)
247   {
248     int nSeq = align.getHeight();
249
250     float[] length = new float[nSeq];
251     SequenceI[] seqs = new SequenceI[nSeq];
252
253     for (int i = 0; i < nSeq; i++)
254     {
255       seqs[i] = align.getSequenceAt(i);
256       length[i] = (float) (seqs[i].getEnd() - seqs[i].getStart());
257     }
258
259     QuickSort.sort(length, seqs);
260
261     if (sortLengthAscending)
262     {
263       setReverseOrder(align, seqs);
264     }
265     else
266     {
267       setOrder(align, seqs);
268     }
269
270     sortLengthAscending = !sortLengthAscending;
271   }
272
273   /**
274    * Sorts the alignment by size of group. <br>
275    * Maintains the order of sequences in each group by order in given alignment
276    * object.
277    * 
278    * @param align
279    *          sorts the given alignment object by group
280    */
281   public static void sortByGroup(AlignmentI align)
282   {
283     // MAINTAINS ORIGNAL SEQUENCE ORDER,
284     // ORDERS BY GROUP SIZE
285     Vector groups = new Vector();
286
287     if (groups.hashCode() != lastGroupHash)
288     {
289       sortGroupAscending = true;
290       lastGroupHash = groups.hashCode();
291     }
292     else
293     {
294       sortGroupAscending = !sortGroupAscending;
295     }
296
297     // SORTS GROUPS BY SIZE
298     // ////////////////////
299     for (SequenceGroup sg:align.getGroups())
300     {
301       for (int j = 0; j < groups.size(); j++)
302       {
303         SequenceGroup sg2 = (SequenceGroup) groups.elementAt(j);
304
305         if (sg.getSize() > sg2.getSize())
306         {
307           groups.insertElementAt(sg, j);
308
309           break;
310         }
311       }
312
313       if (!groups.contains(sg))
314       {
315         groups.addElement(sg);
316       }
317     }
318
319     // NOW ADD SEQUENCES MAINTAINING ALIGNMENT ORDER
320     // /////////////////////////////////////////////
321     Vector seqs = new Vector();
322
323     for (int i = 0; i < groups.size(); i++)
324     {
325       SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
326       SequenceI[] orderedseqs = sg.getSequencesInOrder(align);
327
328       for (int j = 0; j < orderedseqs.length; j++)
329       {
330         seqs.addElement(orderedseqs[j]);
331       }
332     }
333
334     if (sortGroupAscending)
335     {
336       setOrder(align, seqs);
337     }
338     else
339     {
340       setReverseOrder(align,
341               vectorSubsetToArray(seqs, align.getSequences()));
342     }
343   }
344
345   /**
346    * Converts Vector to array. java 1.18 does not have Vector.toArray()
347    * 
348    * @param tmp
349    *          Vector of SequenceI objects
350    * 
351    * @return array of Sequence[]
352    */
353   private static SequenceI[] vectorToArray(Vector tmp)
354   {
355     SequenceI[] seqs = new SequenceI[tmp.size()];
356
357     for (int i = 0; i < tmp.size(); i++)
358     {
359       seqs[i] = (SequenceI) tmp.elementAt(i);
360     }
361
362     return seqs;
363   }
364
365   /**
366    * Select sequences in order from tmp that is present in mask, and any
367    * remaining seqeunces in mask not in tmp
368    *
369    * @param tmp
370    *          thread safe collection of sequences
371    * @param mask
372    *          thread safe collection of sequences
373    *
374    * @return intersect(tmp,mask)+intersect(complement(tmp),mask)
375    */
376   private static SequenceI[] vectorSubsetToArray(List<SequenceI> tmp,
377           List<SequenceI> mask)
378   {
379     ArrayList<SequenceI> seqs = new ArrayList<SequenceI>();
380     int i, idx;
381     boolean[] tmask = new boolean[mask.size()];
382
383     for (i = 0; i < mask.size(); i++)
384     {
385       tmask[i] = true;
386     }
387
388     for (i = 0; i < tmp.size(); i++)
389     {
390       SequenceI sq = tmp.get(i);
391       idx = mask.indexOf(sq);
392       if (idx > -1 && tmask[idx])
393       {
394         tmask[idx] = false;
395         seqs.add(sq);
396       }
397     }
398
399     for (i = 0; i < tmask.length; i++)
400     {
401       if (tmask[i])
402       {
403         seqs.add(mask.get(i));
404       }
405     }
406
407     return seqs.toArray(new SequenceI[seqs.size()]);
408   }
409
410   /**
411    * Sorts by a given AlignmentOrder object
412    * 
413    * @param align
414    *          Alignment to order
415    * @param order
416    *          specified order for alignment
417    */
418   public static void sortBy(AlignmentI align, AlignmentOrder order)
419   {
420     // Get an ordered vector of sequences which may also be present in align
421     Vector tmp = order.getOrder();
422
423     if (lastOrder == order)
424     {
425       sortOrderAscending = !sortOrderAscending;
426     }
427     else
428     {
429       sortOrderAscending = true;
430     }
431
432     if (sortOrderAscending)
433     {
434       setOrder(align, tmp);
435     }
436     else
437     {
438       setReverseOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
439     }
440   }
441
442   /**
443    * DOCUMENT ME!
444    * 
445    * @param align
446    *          alignment to order
447    * @param tree
448    *          tree which has
449    * 
450    * @return DOCUMENT ME!
451    */
452   private static Vector getOrderByTree(AlignmentI align, NJTree tree)
453   {
454     int nSeq = align.getHeight();
455
456     Vector tmp = new Vector();
457
458     tmp = _sortByTree(tree.getTopNode(), tmp, align.getSequences());
459
460     if (tmp.size() != nSeq)
461     {
462       // TODO: JBPNote - decide if this is always an error
463       // (eg. not when a tree is associated to another alignment which has more
464       // sequences)
465       if (tmp.size() != nSeq)
466       {
467         addStrays(align, tmp);
468       }
469
470       if (tmp.size() != nSeq)
471       {
472         System.err.println("WARNING: tmp.size()=" + tmp.size() + " != nseq="
473                 + nSeq + " in getOrderByTree - tree contains sequences not in alignment");
474       }
475     }
476
477     return tmp;
478   }
479
480   /**
481    * Sorts the alignment by a given tree
482    * 
483    * @param align
484    *          alignment to order
485    * @param tree
486    *          tree which has
487    */
488   public static void sortByTree(AlignmentI align, NJTree tree)
489   {
490     Vector tmp = getOrderByTree(align, tree);
491
492     // tmp should properly permute align with tree.
493     if (lastTree != tree)
494     {
495       sortTreeAscending = true;
496       lastTree = tree;
497     }
498     else
499     {
500       sortTreeAscending = !sortTreeAscending;
501     }
502
503     if (sortTreeAscending)
504     {
505       setOrder(align, tmp);
506     }
507     else
508     {
509       setReverseOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
510     }
511   }
512
513   /**
514    * DOCUMENT ME!
515    * 
516    * @param align
517    *          DOCUMENT ME!
518    * @param seqs
519    *          DOCUMENT ME!
520    */
521   private static void addStrays(AlignmentI align, Vector seqs)
522   {
523     int nSeq = align.getHeight();
524
525     for (int i = 0; i < nSeq; i++)
526     {
527       if (!seqs.contains(align.getSequenceAt(i)))
528       {
529         seqs.addElement(align.getSequenceAt(i));
530       }
531     }
532
533     if (nSeq != seqs.size())
534     {
535       System.err
536               .println("ERROR: Size still not right even after addStrays");
537     }
538   }
539
540   /**
541    * DOCUMENT ME!
542    * 
543    * @param node
544    *          DOCUMENT ME!
545    * @param tmp
546    *          DOCUMENT ME!
547    * @param seqset
548    *          DOCUMENT ME!
549    * 
550    * @return DOCUMENT ME!
551    */
552   private static Vector _sortByTree(SequenceNode node, Vector tmp,
553           List<SequenceI> seqset)
554   {
555     if (node == null)
556     {
557       return tmp;
558     }
559
560     SequenceNode left = (SequenceNode) node.left();
561     SequenceNode right = (SequenceNode) node.right();
562
563     if ((left == null) && (right == null))
564     {
565       if (!node.isPlaceholder() && (node.element() != null))
566       {
567         if (node.element() instanceof SequenceI)
568         {
569           if (!tmp.contains(node.element())) //  && (seqset==null || seqset.size()==0 || seqset.contains(tmp)))
570           {
571             tmp.addElement((SequenceI) node.element());
572           }
573         }
574       }
575
576       return tmp;
577     }
578     else
579     {
580       _sortByTree(left, tmp, seqset);
581       _sortByTree(right, tmp, seqset);
582     }
583
584     return tmp;
585   }
586
587   // Ordering Objects
588   // Alignment.sortBy(OrderObj) - sequence of sequence pointer refs in
589   // appropriate order
590   //
591
592   /**
593    * recover the order of sequences given by the safe numbering scheme introducd
594    * SeqsetUtils.uniquify.
595    */
596   public static void recoverOrder(SequenceI[] alignment)
597   {
598     float[] ids = new float[alignment.length];
599
600     for (int i = 0; i < alignment.length; i++)
601     {
602       ids[i] = (new Float(alignment[i].getName().substring(8)))
603               .floatValue();
604     }
605
606     jalview.util.QuickSort.sort(ids, alignment);
607   }
608
609   /**
610    * Sort sequence in order of increasing score attribute for annotation with a
611    * particular scoreLabel. Or reverse if same label was used previously
612    * 
613    * @param scoreLabel
614    *          exact label for sequence associated AlignmentAnnotation scores to
615    *          use for sorting.
616    * @param alignment
617    *          sequences to be sorted
618    */
619   public static void sortByAnnotationScore(String scoreLabel,
620           AlignmentI alignment)
621   {
622     SequenceI[] seqs = alignment.getSequencesArray();
623     boolean[] hasScore = new boolean[seqs.length]; // per sequence score
624     // presence
625     int hasScores = 0; // number of scores present on set
626     double[] scores = new double[seqs.length];
627     double min = 0, max = 0;
628     for (int i = 0; i < seqs.length; i++)
629     {
630       AlignmentAnnotation[] scoreAnn = seqs[i].getAnnotation(scoreLabel);
631       if (scoreAnn != null)
632       {
633         hasScores++;
634         hasScore[i] = true;
635         scores[i] = scoreAnn[0].getScore(); // take the first instance of this
636         // score.
637         if (hasScores == 1)
638         {
639           max = min = scores[i];
640         }
641         else
642         {
643           if (max < scores[i])
644           {
645             max = scores[i];
646           }
647           if (min > scores[i])
648           {
649             min = scores[i];
650           }
651         }
652       }
653       else
654       {
655         hasScore[i] = false;
656       }
657     }
658     if (hasScores == 0)
659     {
660       return; // do nothing - no scores present to sort by.
661     }
662     if (hasScores < seqs.length)
663     {
664       for (int i = 0; i < seqs.length; i++)
665       {
666         if (!hasScore[i])
667         {
668           scores[i] = (max + i + 1.0);
669         }
670       }
671     }
672
673     jalview.util.QuickSort.sort(scores, seqs);
674     if (lastSortByScore != scoreLabel)
675     {
676       lastSortByScore = scoreLabel;
677       setOrder(alignment, seqs);
678     }
679     else
680     {
681       setReverseOrder(alignment, seqs);
682     }
683   }
684
685   /**
686    * types of feature ordering: Sort by score : average score - or total score -
687    * over all features in region Sort by feature label text: (or if null -
688    * feature type text) - numerical or alphabetical Sort by feature density:
689    * based on counts - ignoring individual text or scores for each feature
690    */
691   public static String FEATURE_SCORE = "average_score";
692
693   public static String FEATURE_LABEL = "text";
694
695   public static String FEATURE_DENSITY = "density";
696
697   /**
698    * sort the alignment using the features on each sequence found between start
699    * and stop with the given featureLabel (and optional group qualifier)
700    * 
701    * @param featureLabel
702    *          (may not be null)
703    * @param groupLabel
704    *          (may be null)
705    * @param start
706    *          (-1 to include non-positional features)
707    * @param stop
708    *          (-1 to only sort on non-positional features)
709    * @param alignment
710    *          - aligned sequences containing features
711    * @param method
712    *          - one of the string constants FEATURE_SCORE, FEATURE_LABEL,
713    *          FEATURE_DENSITY
714    */
715   public static void sortByFeature(String featureLabel, String groupLabel,
716           int start, int stop, AlignmentI alignment, String method)
717   {
718     sortByFeature(featureLabel == null ? null : new String[]
719     { featureLabel }, groupLabel == null ? null : new String[]
720     { groupLabel }, start, stop, alignment, method);
721   }
722
723   private static boolean containsIgnoreCase(final String lab,
724           final String[] labs)
725   {
726     if (labs == null)
727     {
728       return true;
729     }
730     if (lab == null)
731     {
732       return false;
733     }
734     for (int q = 0; q < labs.length; q++)
735     {
736       if (labs[q] != null && lab.equalsIgnoreCase(labs[q]))
737       {
738         return true;
739       }
740     }
741     return false;
742   }
743
744   public static void sortByFeature(String[] featureLabels,
745           String[] groupLabels, int start, int stop, AlignmentI alignment,
746           String method)
747   {
748     if (method != FEATURE_SCORE && method != FEATURE_LABEL
749             && method != FEATURE_DENSITY)
750     {
751       throw new Error(
752               "Implementation Error - sortByFeature method must be one of FEATURE_SCORE, FEATURE_LABEL or FEATURE_DENSITY.");
753     }
754     boolean ignoreScore = method != FEATURE_SCORE;
755     StringBuffer scoreLabel = new StringBuffer();
756     scoreLabel.append(start + stop + method);
757     // This doesn't quite work yet - we'd like to have a canonical ordering that
758     // can be preserved from call to call
759     for (int i = 0; featureLabels != null && i < featureLabels.length; i++)
760     {
761       scoreLabel.append(featureLabels[i] == null ? "null"
762               : featureLabels[i]);
763     }
764     for (int i = 0; groupLabels != null && i < groupLabels.length; i++)
765     {
766       scoreLabel.append(groupLabels[i] == null ? "null" : groupLabels[i]);
767     }
768     SequenceI[] seqs = alignment.getSequencesArray();
769
770     boolean[] hasScore = new boolean[seqs.length]; // per sequence score
771     // presence
772     int hasScores = 0; // number of scores present on set
773     double[] scores = new double[seqs.length];
774     int[] seqScores = new int[seqs.length];
775     Object[] feats = new Object[seqs.length];
776     double min = 0, max = 0;
777     for (int i = 0; i < seqs.length; i++)
778     {
779       SequenceFeature[] sf = seqs[i].getSequenceFeatures();
780       if (sf == null && seqs[i].getDatasetSequence() != null)
781       {
782         sf = seqs[i].getDatasetSequence().getSequenceFeatures();
783       }
784       if (sf == null)
785       {
786         sf = new SequenceFeature[0];
787       }
788       else
789       {
790         SequenceFeature[] tmp = new SequenceFeature[sf.length];
791         for (int s = 0; s < tmp.length; s++)
792         {
793           tmp[s] = sf[s];
794         }
795         sf = tmp;
796       }
797       int sstart = (start == -1) ? start : seqs[i].findPosition(start);
798       int sstop = (stop == -1) ? stop : seqs[i].findPosition(stop);
799       seqScores[i] = 0;
800       scores[i] = 0.0;
801       int n = sf.length;
802       for (int f = 0; f < sf.length; f++)
803       {
804         // filter for selection criteria
805         if (
806         // ignore features outwith alignment start-stop positions.
807         (sf[f].end < sstart || sf[f].begin > sstop) ||
808         // or ignore based on selection criteria
809                 (featureLabels != null && !AlignmentSorter
810                         .containsIgnoreCase(sf[f].type, featureLabels))
811                 || (groupLabels != null
812                 // problem here: we cannot eliminate null feature group features
813                 && (sf[f].getFeatureGroup() != null && !AlignmentSorter
814                         .containsIgnoreCase(sf[f].getFeatureGroup(),
815                                 groupLabels))))
816         {
817           // forget about this feature
818           sf[f] = null;
819           n--;
820         }
821         else
822         {
823           // or, also take a look at the scores if necessary.
824           if (!ignoreScore && sf[f].getScore() != Float.NaN)
825           {
826             if (seqScores[i] == 0)
827             {
828               hasScores++;
829             }
830             seqScores[i]++;
831             hasScore[i] = true;
832             scores[i] += sf[f].getScore(); // take the first instance of this
833             // score.
834           }
835         }
836       }
837       SequenceFeature[] fs;
838       feats[i] = fs = new SequenceFeature[n];
839       if (n > 0)
840       {
841         n = 0;
842         for (int f = 0; f < sf.length; f++)
843         {
844           if (sf[f] != null)
845           {
846             ((SequenceFeature[]) feats[i])[n++] = sf[f];
847           }
848         }
849         if (method == FEATURE_LABEL)
850         {
851           // order the labels by alphabet
852           String[] labs = new String[fs.length];
853           for (int l = 0; l < labs.length; l++)
854           {
855             labs[l] = (fs[l].getDescription() != null ? fs[l]
856                     .getDescription() : fs[l].getType());
857           }
858           jalview.util.QuickSort.sort(labs, ((Object[]) feats[i]));
859         }
860       }
861       if (hasScore[i])
862       {
863         // compute average score
864         scores[i] /= seqScores[i];
865         // update the score bounds.
866         if (hasScores == 1)
867         {
868           max = min = scores[i];
869         }
870         else
871         {
872           if (max < scores[i])
873           {
874             max = scores[i];
875           }
876           if (min > scores[i])
877           {
878             min = scores[i];
879           }
880         }
881       }
882     }
883
884     if (method == FEATURE_SCORE)
885     {
886       if (hasScores == 0)
887       {
888         return; // do nothing - no scores present to sort by.
889       }
890       // pad score matrix
891       if (hasScores < seqs.length)
892       {
893         for (int i = 0; i < seqs.length; i++)
894         {
895           if (!hasScore[i])
896           {
897             scores[i] = (max + 1 + i);
898           }
899           else
900           {
901             int nf = (feats[i] == null) ? 0
902                     : ((SequenceFeature[]) feats[i]).length;
903             // System.err.println("Sorting on Score: seq "+seqs[i].getName()+
904             // " Feats: "+nf+" Score : "+scores[i]);
905           }
906         }
907       }
908
909       jalview.util.QuickSort.sort(scores, seqs);
910     }
911     else if (method == FEATURE_DENSITY)
912     {
913
914       // break ties between equivalent numbers for adjacent sequences by adding
915       // 1/Nseq*i on the original order
916       double fr = 0.9 / (1.0 * seqs.length);
917       for (int i = 0; i < seqs.length; i++)
918       {
919         double nf;
920         scores[i] = (0.05 + fr * i)
921                 + (nf = ((feats[i] == null) ? 0.0
922                         : 1.0 * ((SequenceFeature[]) feats[i]).length));
923         // System.err.println("Sorting on Density: seq "+seqs[i].getName()+
924         // " Feats: "+nf+" Score : "+scores[i]);
925       }
926       jalview.util.QuickSort.sort(scores, seqs);
927     }
928     else
929     {
930       if (method == FEATURE_LABEL)
931       {
932         throw new Error("Not yet implemented.");
933       }
934     }
935     if (lastSortByFeatureScore == null
936             || !scoreLabel.toString().equals(lastSortByFeatureScore))
937     {
938       sortByFeatureScoreAscending = true;
939     }
940     else
941     {
942       sortByFeatureScoreAscending = !sortByFeatureScoreAscending;
943     }
944     if (sortByFeatureScoreAscending)
945     {
946       setOrder(alignment, seqs);
947     }
948     else
949     {
950       setReverseOrder(alignment, seqs);
951     }
952     lastSortByFeatureScore = scoreLabel.toString();
953   }
954
955 }