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