JAL-1114 - refactor methods handling Vectors and Hashtables to Lists and Maps, and...
[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     for (int i = 0; i < len; i++)
150     {
151       // SequenceI tmp = seqs[i];
152       align.getSequences().setElementAt(seqs[nSeq - i - 1], i);
153       align.getSequences().setElementAt(seqs[i], nSeq - i - 1);
154     }
155   }
156
157   /**
158    * Sets the Alignment object with the given sequences
159    * 
160    * @param align
161    *          Alignment object to be updated
162    * @param tmp
163    *          sequences as a vector
164    */
165   private static void setOrder(AlignmentI align, Vector tmp)
166   {
167     setOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
168   }
169
170   /**
171    * Sets the Alignment object with the given sequences
172    * 
173    * @param align
174    *          DOCUMENT ME!
175    * @param seqs
176    *          sequences as an array
177    */
178   public static void setOrder(AlignmentI align, SequenceI[] seqs)
179   {
180     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
181     Vector algn = align.getSequences();
182     Vector tmp = new Vector();
183
184     for (int i = 0; i < seqs.length; i++)
185     {
186       if (algn.contains(seqs[i]))
187       {
188         tmp.addElement(seqs[i]);
189       }
190     }
191
192     algn.removeAllElements();
193     // User may have hidden seqs, then clicked undo or redo
194     for (int i = 0; i < tmp.size(); i++)
195     {
196       algn.addElement(tmp.elementAt(i));
197     }
198
199   }
200
201   /**
202    * Sorts by ID. Numbers are sorted before letters.
203    * 
204    * @param align
205    *          The alignment object to sort
206    */
207   public static void sortByID(AlignmentI align)
208   {
209     int nSeq = align.getHeight();
210
211     String[] ids = new String[nSeq];
212     SequenceI[] seqs = new SequenceI[nSeq];
213
214     for (int i = 0; i < nSeq; i++)
215     {
216       ids[i] = align.getSequenceAt(i).getName();
217       seqs[i] = align.getSequenceAt(i);
218     }
219
220     QuickSort.sort(ids, seqs);
221
222     if (sortIdAscending)
223     {
224       setReverseOrder(align, seqs);
225     }
226     else
227     {
228       setOrder(align, seqs);
229     }
230
231     sortIdAscending = !sortIdAscending;
232   }
233
234   /**
235    * Sorts by sequence length
236    * 
237    * @param align
238    *          The alignment object to sort
239    */
240   public static void sortByLength(AlignmentI align)
241   {
242     int nSeq = align.getHeight();
243
244     float[] length = new float[nSeq];
245     SequenceI[] seqs = new SequenceI[nSeq];
246
247     for (int i = 0; i < nSeq; i++)
248     {
249       seqs[i] = align.getSequenceAt(i);
250       length[i] = (float) (seqs[i].getEnd() - seqs[i].getStart());
251     }
252
253     QuickSort.sort(length, seqs);
254
255     if (sortLengthAscending)
256     {
257       setReverseOrder(align, seqs);
258     }
259     else
260     {
261       setOrder(align, seqs);
262     }
263
264     sortLengthAscending = !sortLengthAscending;
265   }
266
267   /**
268    * Sorts the alignment by size of group. <br>
269    * Maintains the order of sequences in each group by order in given alignment
270    * object.
271    * 
272    * @param align
273    *          sorts the given alignment object by group
274    */
275   public static void sortByGroup(AlignmentI align)
276   {
277     // MAINTAINS ORIGNAL SEQUENCE ORDER,
278     // ORDERS BY GROUP SIZE
279     Vector groups = new Vector();
280
281     if (groups.hashCode() != lastGroupHash)
282     {
283       sortGroupAscending = true;
284       lastGroupHash = groups.hashCode();
285     }
286     else
287     {
288       sortGroupAscending = !sortGroupAscending;
289     }
290
291     // SORTS GROUPS BY SIZE
292     // ////////////////////
293     for (SequenceGroup sg:align.getGroups())
294     {
295       for (int j = 0; j < groups.size(); j++)
296       {
297         SequenceGroup sg2 = (SequenceGroup) groups.elementAt(j);
298
299         if (sg.getSize() > sg2.getSize())
300         {
301           groups.insertElementAt(sg, j);
302
303           break;
304         }
305       }
306
307       if (!groups.contains(sg))
308       {
309         groups.addElement(sg);
310       }
311     }
312
313     // NOW ADD SEQUENCES MAINTAINING ALIGNMENT ORDER
314     // /////////////////////////////////////////////
315     Vector seqs = new Vector();
316
317     for (int i = 0; i < groups.size(); i++)
318     {
319       SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
320       SequenceI[] orderedseqs = sg.getSequencesInOrder(align);
321
322       for (int j = 0; j < orderedseqs.length; j++)
323       {
324         seqs.addElement(orderedseqs[j]);
325       }
326     }
327
328     if (sortGroupAscending)
329     {
330       setOrder(align, seqs);
331     }
332     else
333     {
334       setReverseOrder(align,
335               vectorSubsetToArray(seqs, align.getSequences()));
336     }
337   }
338
339   /**
340    * Converts Vector to array. java 1.18 does not have Vector.toArray()
341    * 
342    * @param tmp
343    *          Vector of SequenceI objects
344    * 
345    * @return array of Sequence[]
346    */
347   private static SequenceI[] vectorToArray(Vector tmp)
348   {
349     SequenceI[] seqs = new SequenceI[tmp.size()];
350
351     for (int i = 0; i < tmp.size(); i++)
352     {
353       seqs[i] = (SequenceI) tmp.elementAt(i);
354     }
355
356     return seqs;
357   }
358
359   /**
360    * DOCUMENT ME!
361    * 
362    * @param tmp
363    *          DOCUMENT ME!
364    * @param mask
365    *          DOCUMENT ME!
366    * 
367    * @return DOCUMENT ME!
368    */
369   private static SequenceI[] vectorSubsetToArray(Vector tmp, Vector mask)
370   {
371     Vector seqs = new Vector();
372     int i, idx;
373     boolean[] tmask = new boolean[mask.size()];
374
375     for (i = 0; i < mask.size(); i++)
376     {
377       tmask[i] = true;
378     }
379
380     for (i = 0; i < tmp.size(); i++)
381     {
382       Object sq = tmp.elementAt(i);
383       idx = mask.indexOf(sq);
384       if (idx > -1 && tmask[idx])
385       {
386         tmask[idx] = false;
387         seqs.addElement(sq);
388       }
389     }
390
391     for (i = 0; i < tmask.length; i++)
392     {
393       if (tmask[i])
394       {
395         seqs.addElement(mask.elementAt(i));
396       }
397     }
398
399     return vectorToArray(seqs);
400   }
401
402   /**
403    * Sorts by a given AlignmentOrder object
404    * 
405    * @param align
406    *          Alignment to order
407    * @param order
408    *          specified order for alignment
409    */
410   public static void sortBy(AlignmentI align, AlignmentOrder order)
411   {
412     // Get an ordered vector of sequences which may also be present in align
413     Vector tmp = order.getOrder();
414
415     if (lastOrder == order)
416     {
417       sortOrderAscending = !sortOrderAscending;
418     }
419     else
420     {
421       sortOrderAscending = true;
422     }
423
424     if (sortOrderAscending)
425     {
426       setOrder(align, tmp);
427     }
428     else
429     {
430       setReverseOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
431     }
432   }
433
434   /**
435    * DOCUMENT ME!
436    * 
437    * @param align
438    *          alignment to order
439    * @param tree
440    *          tree which has
441    * 
442    * @return DOCUMENT ME!
443    */
444   private static Vector getOrderByTree(AlignmentI align, NJTree tree)
445   {
446     int nSeq = align.getHeight();
447
448     Vector tmp = new Vector();
449
450     tmp = _sortByTree(tree.getTopNode(), tmp, align.getSequences());
451
452     if (tmp.size() != nSeq)
453     {
454       // TODO: JBPNote - decide if this is always an error
455       // (eg. not when a tree is associated to another alignment which has more
456       // sequences)
457       if (tmp.size() != nSeq)
458       {
459         addStrays(align, tmp);
460       }
461
462       if (tmp.size() != nSeq)
463       {
464         System.err.println("WARNING: tmp.size()=" + tmp.size() + " != nseq="
465                 + nSeq + " in getOrderByTree - tree contains sequences not in alignment");
466       }
467     }
468
469     return tmp;
470   }
471
472   /**
473    * Sorts the alignment by a given tree
474    * 
475    * @param align
476    *          alignment to order
477    * @param tree
478    *          tree which has
479    */
480   public static void sortByTree(AlignmentI align, NJTree tree)
481   {
482     Vector tmp = getOrderByTree(align, tree);
483
484     // tmp should properly permute align with tree.
485     if (lastTree != tree)
486     {
487       sortTreeAscending = true;
488       lastTree = tree;
489     }
490     else
491     {
492       sortTreeAscending = !sortTreeAscending;
493     }
494
495     if (sortTreeAscending)
496     {
497       setOrder(align, tmp);
498     }
499     else
500     {
501       setReverseOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
502     }
503   }
504
505   /**
506    * DOCUMENT ME!
507    * 
508    * @param align
509    *          DOCUMENT ME!
510    * @param seqs
511    *          DOCUMENT ME!
512    */
513   private static void addStrays(AlignmentI align, Vector seqs)
514   {
515     int nSeq = align.getHeight();
516
517     for (int i = 0; i < nSeq; i++)
518     {
519       if (!seqs.contains(align.getSequenceAt(i)))
520       {
521         seqs.addElement(align.getSequenceAt(i));
522       }
523     }
524
525     if (nSeq != seqs.size())
526     {
527       System.err
528               .println("ERROR: Size still not right even after addStrays");
529     }
530   }
531
532   /**
533    * DOCUMENT ME!
534    * 
535    * @param node
536    *          DOCUMENT ME!
537    * @param tmp
538    *          DOCUMENT ME!
539    * @param seqset
540    *          DOCUMENT ME!
541    * 
542    * @return DOCUMENT ME!
543    */
544   private static Vector _sortByTree(SequenceNode node, Vector tmp,
545           Vector seqset)
546   {
547     if (node == null)
548     {
549       return tmp;
550     }
551
552     SequenceNode left = (SequenceNode) node.left();
553     SequenceNode right = (SequenceNode) node.right();
554
555     if ((left == null) && (right == null))
556     {
557       if (!node.isPlaceholder() && (node.element() != null))
558       {
559         if (node.element() instanceof SequenceI)
560         {
561           if (!tmp.contains(node.element())) //  && (seqset==null || seqset.size()==0 || seqset.contains(tmp)))
562           {
563             tmp.addElement((SequenceI) node.element());
564           }
565         }
566       }
567
568       return tmp;
569     }
570     else
571     {
572       _sortByTree(left, tmp, seqset);
573       _sortByTree(right, tmp, seqset);
574     }
575
576     return tmp;
577   }
578
579   // Ordering Objects
580   // Alignment.sortBy(OrderObj) - sequence of sequence pointer refs in
581   // appropriate order
582   //
583
584   /**
585    * recover the order of sequences given by the safe numbering scheme introducd
586    * SeqsetUtils.uniquify.
587    */
588   public static void recoverOrder(SequenceI[] alignment)
589   {
590     float[] ids = new float[alignment.length];
591
592     for (int i = 0; i < alignment.length; i++)
593     {
594       ids[i] = (new Float(alignment[i].getName().substring(8)))
595               .floatValue();
596     }
597
598     jalview.util.QuickSort.sort(ids, alignment);
599   }
600
601   /**
602    * Sort sequence in order of increasing score attribute for annotation with a
603    * particular scoreLabel. Or reverse if same label was used previously
604    * 
605    * @param scoreLabel
606    *          exact label for sequence associated AlignmentAnnotation scores to
607    *          use for sorting.
608    * @param alignment
609    *          sequences to be sorted
610    */
611   public static void sortByAnnotationScore(String scoreLabel,
612           AlignmentI alignment)
613   {
614     SequenceI[] seqs = alignment.getSequencesArray();
615     boolean[] hasScore = new boolean[seqs.length]; // per sequence score
616     // presence
617     int hasScores = 0; // number of scores present on set
618     double[] scores = new double[seqs.length];
619     double min = 0, max = 0;
620     for (int i = 0; i < seqs.length; i++)
621     {
622       AlignmentAnnotation[] scoreAnn = seqs[i].getAnnotation(scoreLabel);
623       if (scoreAnn != null)
624       {
625         hasScores++;
626         hasScore[i] = true;
627         scores[i] = scoreAnn[0].getScore(); // take the first instance of this
628         // score.
629         if (hasScores == 1)
630         {
631           max = min = scores[i];
632         }
633         else
634         {
635           if (max < scores[i])
636           {
637             max = scores[i];
638           }
639           if (min > scores[i])
640           {
641             min = scores[i];
642           }
643         }
644       }
645       else
646       {
647         hasScore[i] = false;
648       }
649     }
650     if (hasScores == 0)
651     {
652       return; // do nothing - no scores present to sort by.
653     }
654     if (hasScores < seqs.length)
655     {
656       for (int i = 0; i < seqs.length; i++)
657       {
658         if (!hasScore[i])
659         {
660           scores[i] = (max + i + 1.0);
661         }
662       }
663     }
664
665     jalview.util.QuickSort.sort(scores, seqs);
666     if (lastSortByScore != scoreLabel)
667     {
668       lastSortByScore = scoreLabel;
669       setOrder(alignment, seqs);
670     }
671     else
672     {
673       setReverseOrder(alignment, seqs);
674     }
675   }
676
677   /**
678    * types of feature ordering: Sort by score : average score - or total score -
679    * over all features in region Sort by feature label text: (or if null -
680    * feature type text) - numerical or alphabetical Sort by feature density:
681    * based on counts - ignoring individual text or scores for each feature
682    */
683   public static String FEATURE_SCORE = "average_score";
684
685   public static String FEATURE_LABEL = "text";
686
687   public static String FEATURE_DENSITY = "density";
688
689   /**
690    * sort the alignment using the features on each sequence found between start
691    * and stop with the given featureLabel (and optional group qualifier)
692    * 
693    * @param featureLabel
694    *          (may not be null)
695    * @param groupLabel
696    *          (may be null)
697    * @param start
698    *          (-1 to include non-positional features)
699    * @param stop
700    *          (-1 to only sort on non-positional features)
701    * @param alignment
702    *          - aligned sequences containing features
703    * @param method
704    *          - one of the string constants FEATURE_SCORE, FEATURE_LABEL,
705    *          FEATURE_DENSITY
706    */
707   public static void sortByFeature(String featureLabel, String groupLabel,
708           int start, int stop, AlignmentI alignment, String method)
709   {
710     sortByFeature(featureLabel == null ? null : new String[]
711     { featureLabel }, groupLabel == null ? null : new String[]
712     { groupLabel }, start, stop, alignment, method);
713   }
714
715   private static boolean containsIgnoreCase(final String lab,
716           final String[] labs)
717   {
718     if (labs == null)
719     {
720       return true;
721     }
722     if (lab == null)
723     {
724       return false;
725     }
726     for (int q = 0; q < labs.length; q++)
727     {
728       if (labs[q] != null && lab.equalsIgnoreCase(labs[q]))
729       {
730         return true;
731       }
732     }
733     return false;
734   }
735
736   public static void sortByFeature(String[] featureLabels,
737           String[] groupLabels, int start, int stop, AlignmentI alignment,
738           String method)
739   {
740     if (method != FEATURE_SCORE && method != FEATURE_LABEL
741             && method != FEATURE_DENSITY)
742     {
743       throw new Error(
744               "Implementation Error - sortByFeature method must be one of FEATURE_SCORE, FEATURE_LABEL or FEATURE_DENSITY.");
745     }
746     boolean ignoreScore = method != FEATURE_SCORE;
747     StringBuffer scoreLabel = new StringBuffer();
748     scoreLabel.append(start + stop + method);
749     // This doesn't quite work yet - we'd like to have a canonical ordering that
750     // can be preserved from call to call
751     for (int i = 0; featureLabels != null && i < featureLabels.length; i++)
752     {
753       scoreLabel.append(featureLabels[i] == null ? "null"
754               : featureLabels[i]);
755     }
756     for (int i = 0; groupLabels != null && i < groupLabels.length; i++)
757     {
758       scoreLabel.append(groupLabels[i] == null ? "null" : groupLabels[i]);
759     }
760     SequenceI[] seqs = alignment.getSequencesArray();
761
762     boolean[] hasScore = new boolean[seqs.length]; // per sequence score
763     // presence
764     int hasScores = 0; // number of scores present on set
765     double[] scores = new double[seqs.length];
766     int[] seqScores = new int[seqs.length];
767     Object[] feats = new Object[seqs.length];
768     double min = 0, max = 0;
769     for (int i = 0; i < seqs.length; i++)
770     {
771       SequenceFeature[] sf = seqs[i].getSequenceFeatures();
772       if (sf == null && seqs[i].getDatasetSequence() != null)
773       {
774         sf = seqs[i].getDatasetSequence().getSequenceFeatures();
775       }
776       if (sf == null)
777       {
778         sf = new SequenceFeature[0];
779       }
780       else
781       {
782         SequenceFeature[] tmp = new SequenceFeature[sf.length];
783         for (int s = 0; s < tmp.length; s++)
784         {
785           tmp[s] = sf[s];
786         }
787         sf = tmp;
788       }
789       int sstart = (start == -1) ? start : seqs[i].findPosition(start);
790       int sstop = (stop == -1) ? stop : seqs[i].findPosition(stop);
791       seqScores[i] = 0;
792       scores[i] = 0.0;
793       int n = sf.length;
794       for (int f = 0; f < sf.length; f++)
795       {
796         // filter for selection criteria
797         if (
798         // ignore features outwith alignment start-stop positions.
799         (sf[f].end < sstart || sf[f].begin > sstop) ||
800         // or ignore based on selection criteria
801                 (featureLabels != null && !AlignmentSorter
802                         .containsIgnoreCase(sf[f].type, featureLabels))
803                 || (groupLabels != null
804                 // problem here: we cannot eliminate null feature group features
805                 && (sf[f].getFeatureGroup() != null && !AlignmentSorter
806                         .containsIgnoreCase(sf[f].getFeatureGroup(),
807                                 groupLabels))))
808         {
809           // forget about this feature
810           sf[f] = null;
811           n--;
812         }
813         else
814         {
815           // or, also take a look at the scores if necessary.
816           if (!ignoreScore && sf[f].getScore() != Float.NaN)
817           {
818             if (seqScores[i] == 0)
819             {
820               hasScores++;
821             }
822             seqScores[i]++;
823             hasScore[i] = true;
824             scores[i] += sf[f].getScore(); // take the first instance of this
825             // score.
826           }
827         }
828       }
829       SequenceFeature[] fs;
830       feats[i] = fs = new SequenceFeature[n];
831       if (n > 0)
832       {
833         n = 0;
834         for (int f = 0; f < sf.length; f++)
835         {
836           if (sf[f] != null)
837           {
838             ((SequenceFeature[]) feats[i])[n++] = sf[f];
839           }
840         }
841         if (method == FEATURE_LABEL)
842         {
843           // order the labels by alphabet
844           String[] labs = new String[fs.length];
845           for (int l = 0; l < labs.length; l++)
846           {
847             labs[l] = (fs[l].getDescription() != null ? fs[l]
848                     .getDescription() : fs[l].getType());
849           }
850           jalview.util.QuickSort.sort(labs, ((Object[]) feats[i]));
851         }
852       }
853       if (hasScore[i])
854       {
855         // compute average score
856         scores[i] /= seqScores[i];
857         // update the score bounds.
858         if (hasScores == 1)
859         {
860           max = min = scores[i];
861         }
862         else
863         {
864           if (max < scores[i])
865           {
866             max = scores[i];
867           }
868           if (min > scores[i])
869           {
870             min = scores[i];
871           }
872         }
873       }
874     }
875
876     if (method == FEATURE_SCORE)
877     {
878       if (hasScores == 0)
879       {
880         return; // do nothing - no scores present to sort by.
881       }
882       // pad score matrix
883       if (hasScores < seqs.length)
884       {
885         for (int i = 0; i < seqs.length; i++)
886         {
887           if (!hasScore[i])
888           {
889             scores[i] = (max + 1 + i);
890           }
891           else
892           {
893             int nf = (feats[i] == null) ? 0
894                     : ((SequenceFeature[]) feats[i]).length;
895             // System.err.println("Sorting on Score: seq "+seqs[i].getName()+
896             // " Feats: "+nf+" Score : "+scores[i]);
897           }
898         }
899       }
900
901       jalview.util.QuickSort.sort(scores, seqs);
902     }
903     else if (method == FEATURE_DENSITY)
904     {
905
906       // break ties between equivalent numbers for adjacent sequences by adding
907       // 1/Nseq*i on the original order
908       double fr = 0.9 / (1.0 * seqs.length);
909       for (int i = 0; i < seqs.length; i++)
910       {
911         double nf;
912         scores[i] = (0.05 + fr * i)
913                 + (nf = ((feats[i] == null) ? 0.0
914                         : 1.0 * ((SequenceFeature[]) feats[i]).length));
915         // System.err.println("Sorting on Density: seq "+seqs[i].getName()+
916         // " Feats: "+nf+" Score : "+scores[i]);
917       }
918       jalview.util.QuickSort.sort(scores, seqs);
919     }
920     else
921     {
922       if (method == FEATURE_LABEL)
923       {
924         throw new Error("Not yet implemented.");
925       }
926     }
927     if (lastSortByFeatureScore == null
928             || !scoreLabel.toString().equals(lastSortByFeatureScore))
929     {
930       sortByFeatureScoreAscending = true;
931     }
932     else
933     {
934       sortByFeatureScoreAscending = !sortByFeatureScoreAscending;
935     }
936     if (sortByFeatureScoreAscending)
937     {
938       setOrder(alignment, seqs);
939     }
940     else
941     {
942       setReverseOrder(alignment, seqs);
943     }
944     lastSortByFeatureScore = scoreLabel.toString();
945   }
946
947 }