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