JAL-2490 initial unit tests prior to refactoring
[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   private static boolean containsIgnoreCase(final String lab,
686           final List<String> labs)
687   {
688     if (labs == null)
689     {
690       return true;
691     }
692     if (lab == null)
693     {
694       return false;
695     }
696     for (String label : labs)
697     {
698       if (lab.equalsIgnoreCase(label))
699       {
700         return true;
701       }
702     }
703     return false;
704   }
705
706   public static void sortByFeature(List<String> featureLabels,
707           List<String> groupLabels, int start, int stop,
708           AlignmentI alignment, String method)
709   {
710     if (method != FEATURE_SCORE && method != FEATURE_LABEL
711             && method != FEATURE_DENSITY)
712     {
713       throw new Error(
714               MessageManager
715                       .getString("error.implementation_error_sortbyfeature"));
716     }
717
718     boolean ignoreScore = method != FEATURE_SCORE;
719     StringBuffer scoreLabel = new StringBuffer();
720     scoreLabel.append(start + stop + method);
721     // This doesn't quite work yet - we'd like to have a canonical ordering that
722     // can be preserved from call to call
723     if (featureLabels != null)
724     {
725       for (String label : featureLabels)
726       {
727         scoreLabel.append(label);
728       }
729     }
730     if (groupLabels != null)
731     {
732       for (String label : groupLabels)
733       {
734         scoreLabel.append(label);
735       }
736     }
737
738     /*
739      * if resorting the same feature, toggle sort order
740      */
741     if (lastSortByFeatureScore == null
742             || !scoreLabel.toString().equals(lastSortByFeatureScore))
743     {
744       sortByFeatureScoreAscending = true;
745     }
746     else
747     {
748       sortByFeatureScoreAscending = !sortByFeatureScoreAscending;
749     }
750     lastSortByFeatureScore = scoreLabel.toString();
751
752     SequenceI[] seqs = alignment.getSequencesArray();
753
754     boolean[] hasScore = new boolean[seqs.length]; // per sequence score
755     // presence
756     int hasScores = 0; // number of scores present on set
757     double[] scores = new double[seqs.length];
758     int[] seqScores = new int[seqs.length];
759     Object[] feats = new Object[seqs.length];
760     double min = 0, max = 0;
761     for (int i = 0; i < seqs.length; i++)
762     {
763       SequenceFeature[] sf = seqs[i].getSequenceFeatures();
764       if (sf == null)
765       {
766         sf = new SequenceFeature[0];
767       }
768       else
769       {
770         SequenceFeature[] tmp = new SequenceFeature[sf.length];
771         for (int s = 0; s < tmp.length; s++)
772         {
773           tmp[s] = sf[s];
774         }
775         sf = tmp;
776       }
777       int sstart = (start == -1) ? start : seqs[i].findPosition(start);
778       int sstop = (stop == -1) ? stop : seqs[i].findPosition(stop);
779       seqScores[i] = 0;
780       scores[i] = 0.0;
781       int n = sf.length;
782       for (int f = 0; f < sf.length; f++)
783       {
784         // filter for selection criteria
785         if (
786         // ignore features outwith alignment start-stop positions.
787         (sf[f].end < sstart || sf[f].begin > sstop) ||
788         // or ignore based on selection criteria
789                 (featureLabels != null && !AlignmentSorter
790                         .containsIgnoreCase(sf[f].type, featureLabels))
791                 || (groupLabels != null
792                 // problem here: we cannot eliminate null feature group features
793                 && (sf[f].getFeatureGroup() != null && !AlignmentSorter
794                         .containsIgnoreCase(sf[f].getFeatureGroup(),
795                                 groupLabels))))
796         {
797           // forget about this feature
798           sf[f] = null;
799           n--;
800         }
801         else
802         {
803           // or, also take a look at the scores if necessary.
804           if (!ignoreScore && !Float.isNaN(sf[f].getScore()))
805           {
806             if (seqScores[i] == 0)
807             {
808               hasScores++;
809             }
810             seqScores[i]++;
811             hasScore[i] = true;
812             scores[i] += sf[f].getScore(); // take the first instance of this
813             // score.
814           }
815         }
816       }
817       SequenceFeature[] fs;
818       feats[i] = fs = new SequenceFeature[n];
819       if (n > 0)
820       {
821         n = 0;
822         for (int f = 0; f < sf.length; f++)
823         {
824           if (sf[f] != null)
825           {
826             ((SequenceFeature[]) feats[i])[n++] = sf[f];
827           }
828         }
829         if (method == FEATURE_LABEL)
830         {
831           // order the labels by alphabet
832           String[] labs = new String[fs.length];
833           for (int l = 0; l < labs.length; l++)
834           {
835             labs[l] = (fs[l].getDescription() != null ? fs[l]
836                     .getDescription() : fs[l].getType());
837           }
838           QuickSort.sort(labs, ((Object[]) feats[i]));
839         }
840       }
841       if (hasScore[i])
842       {
843         // compute average score
844         scores[i] /= seqScores[i];
845         // update the score bounds.
846         if (hasScores == 1)
847         {
848           max = min = scores[i];
849         }
850         else
851         {
852           if (max < scores[i])
853           {
854             max = scores[i];
855           }
856           if (min > scores[i])
857           {
858             min = scores[i];
859           }
860         }
861       }
862     }
863
864     if (method == FEATURE_SCORE)
865     {
866       if (hasScores == 0)
867       {
868         return; // do nothing - no scores present to sort by.
869       }
870       // pad score matrix
871       if (hasScores < seqs.length)
872       {
873         for (int i = 0; i < seqs.length; i++)
874         {
875           if (!hasScore[i])
876           {
877             scores[i] = (max + 1 + i);
878           }
879           else
880           {
881             // int nf = (feats[i] == null) ? 0
882             // : ((SequenceFeature[]) feats[i]).length;
883             // // System.err.println("Sorting on Score: seq " +
884             // seqs[i].getName()
885             // + " Feats: " + nf + " Score : " + scores[i]);
886           }
887         }
888       }
889       QuickSort.sortByDouble(scores, seqs, sortByFeatureScoreAscending);
890     }
891     else if (method == FEATURE_DENSITY)
892     {
893       for (int i = 0; i < seqs.length; i++)
894       {
895         int featureCount = feats[i] == null ? 0
896                 : ((SequenceFeature[]) feats[i]).length;
897         scores[i] = featureCount;
898         // System.err.println("Sorting on Density: seq "+seqs[i].getName()+
899         // " Feats: "+featureCount+" Score : "+scores[i]);
900       }
901       QuickSort.sortByDouble(scores, seqs, sortByFeatureScoreAscending);
902     }
903     else
904     {
905       if (method == FEATURE_LABEL)
906       {
907         throw new Error(
908                 MessageManager.getString("error.not_yet_implemented"));
909       }
910     }
911
912     setOrder(alignment, seqs);
913   }
914
915 }