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