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