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