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