af9b5df67928c1254e813ead74106776bbf78012
[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.Collections;
38 import java.util.Iterator;
39 import java.util.List;
40
41 /**
42  * Routines for manipulating the order of a multiple sequence alignment TODO:
43  * this class retains some global states concerning sort-order which should be
44  * made attributes for the caller's alignment visualization. TODO: refactor to
45  * allow a subset of selected sequences to be sorted within the context of a
46  * whole alignment. Sort method template is: SequenceI[] tobesorted, [ input
47  * data mapping to each tobesorted element to use ], Alignment context of
48  * tobesorted that are to be re-ordered, boolean sortinplace, [special data - ie
49  * seuqence to be sorted w.r.t.]) sortinplace implies that the sorted vector
50  * resulting from applying the operation to tobesorted should be mapped back to
51  * the original positions in alignment. Otherwise, normal behaviour is to re
52  * order alignment so that tobesorted is sorted and grouped together starting
53  * from the first tobesorted position in the alignment. e.g. (a,tb2,b,tb1,c,tb3
54  * becomes a,tb1,tb2,tb3,b,c)
55  */
56 public class AlignmentSorter implements ApplicationSingletonI
57 {
58
59   private AlignmentSorter()
60   {
61     // private singleton
62   }
63
64   public static AlignmentSorter getInstance()
65   {
66     return (AlignmentSorter) ApplicationSingletonProvider
67             .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   /**
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     sort(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     sort(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     sort(align, tmp, as.sortGroupAscending);
261   }
262
263   /**
264    * Select sequences in order from tmp that is present in mask, and any
265    * remaining sequences in mask not in tmp
266    * 
267    * @param tmp
268    *          thread safe collection of sequences
269    * @param mask
270    *          thread safe collection of sequences
271    * 
272    * @return intersect(tmp,mask)+intersect(complement(tmp),mask)
273    */
274   private static SequenceI[] vectorSubsetToArray(List<SequenceI> tmp,
275           List<SequenceI> mask)
276   {
277     // or?
278     // tmp2 = tmp.retainAll(mask);
279     // return tmp2.addAll(mask.removeAll(tmp2))
280
281     ArrayList<SequenceI> seqs = new ArrayList<>();
282     int i, idx;
283     boolean[] tmask = new boolean[mask.size()];
284
285     for (i = 0; i < mask.size(); i++)
286     {
287       tmask[i] = true;
288     }
289
290     for (i = 0; i < tmp.size(); i++)
291     {
292       SequenceI sq = tmp.get(i);
293       idx = mask.indexOf(sq);
294       if (idx > -1 && tmask[idx])
295       {
296         tmask[idx] = false;
297         seqs.add(sq);
298       }
299     }
300
301     for (i = 0; i < tmask.length; i++)
302     {
303       if (tmask[i])
304       {
305         seqs.add(mask.get(i));
306       }
307     }
308
309     return seqs.toArray(new SequenceI[seqs.size()]);
310   }
311
312   /**
313    * Sorts by a given AlignmentOrder object
314    * 
315    * @param align
316    *          Alignment to order
317    * @param order
318    *          specified order for alignment
319    */
320   public static void sortBy(AlignmentI align, AlignmentOrder order)
321   {
322     // Get an ordered vector of sequences which may also be present in align
323     List<SequenceI> tmp = order.getOrder();
324
325     AlignmentSorter as = getInstance();
326
327     if (as.lastOrder == order)
328     {
329       as.sortOrderAscending = !as.sortOrderAscending;
330     }
331     else
332     {
333       as.sortOrderAscending = true;
334     }
335     sort(align, tmp, as.sortOrderAscending);
336   }
337
338   /**
339    * DOCUMENT ME!
340    * 
341    * @param align
342    *          alignment to order
343    * @param tree
344    *          tree which has
345    * 
346    * @return DOCUMENT ME!
347    */
348   private static List<SequenceI> getOrderByTree(AlignmentI align,
349           TreeModel tree)
350   {
351     int nSeq = align.getHeight();
352
353     List<SequenceI> tmp = new ArrayList<>();
354
355     tmp = _sortByTree(tree.getTopNode(), tmp, align.getSequences());
356
357     if (tmp.size() != nSeq)
358     {
359       // TODO: JBPNote - decide if this is always an error
360       // (eg. not when a tree is associated to another alignment which has more
361       // sequences)
362       if (tmp.size() != nSeq)
363       {
364         addStrays(align, tmp);
365       }
366
367       if (tmp.size() != nSeq)
368       {
369         System.err.println("WARNING: tmp.size()=" + tmp.size() + " != nseq="
370                 + nSeq
371                 + " in getOrderByTree - tree contains sequences not in alignment");
372       }
373     }
374
375     return tmp;
376   }
377
378   /**
379    * Sorts the alignment by a given tree
380    * 
381    * @param align
382    *          alignment to order
383    * @param tree
384    *          tree which has
385    */
386   public static void sortByTree(AlignmentI align, TreeModel tree)
387   {
388     List<SequenceI> tmp = getOrderByTree(align, tree);
389
390     AlignmentSorter as = getInstance();
391
392     // tmp should properly permute align with tree.
393     if (as.lastTree != tree)
394     {
395       as.sortTreeAscending = true;
396       as.lastTree = tree;
397     }
398     else
399     {
400       as.sortTreeAscending = !as.sortTreeAscending;
401     }
402     sort(align, tmp, as.sortTreeAscending);
403   }
404
405   /**
406    * DOCUMENT ME!
407    * 
408    * @param align
409    *          DOCUMENT ME!
410    * @param tmp
411    *          DOCUMENT ME!
412    */
413   private static void addStrays(AlignmentI align, List<SequenceI> tmp)
414   {
415     int nSeq = align.getHeight();
416
417     for (int i = 0; i < nSeq; i++)
418     {
419       if (!tmp.contains(align.getSequenceAt(i)))
420       {
421         tmp.add(align.getSequenceAt(i));
422       }
423     }
424
425     if (nSeq != tmp.size())
426     {
427       System.err
428               .println("ERROR: Size still not right even after addStrays");
429     }
430   }
431
432   /**
433    * DOCUMENT ME!
434    * 
435    * @param node
436    *          DOCUMENT ME!
437    * @param tmp
438    *          DOCUMENT ME!
439    * @param seqset
440    *          DOCUMENT ME!
441    * 
442    * @return DOCUMENT ME!
443    */
444   private static List<SequenceI> _sortByTree(SequenceNode node,
445           List<SequenceI> tmp, List<SequenceI> seqset)
446   {
447     if (node == null)
448     {
449       return tmp;
450     }
451
452     SequenceNode left = (SequenceNode) node.left();
453     SequenceNode right = (SequenceNode) node.right();
454
455     if ((left == null) && (right == null))
456     {
457       if (!node.isPlaceholder() && (node.element() != null))
458       {
459         if (node.element() instanceof SequenceI)
460         {
461           if (!tmp.contains(node.element())) // && (seqset==null ||
462                                              // seqset.size()==0 ||
463                                              // seqset.contains(tmp)))
464           {
465             tmp.add((SequenceI) node.element());
466           }
467         }
468       }
469
470       return tmp;
471     }
472     else
473     {
474       _sortByTree(left, tmp, seqset);
475       _sortByTree(right, tmp, seqset);
476     }
477
478     return tmp;
479   }
480
481   // Ordering Objects
482   // Alignment.sortBy(OrderObj) - sequence of sequence pointer refs in
483   // appropriate order
484   //
485
486   /**
487    * recover the order of sequences given by the safe numbering scheme introducd
488    * SeqsetUtils.uniquify.
489    */
490   public static void recoverOrder(SequenceI[] alignment)
491   {
492     float[] ids = new float[alignment.length];
493
494     for (int i = 0; i < alignment.length; i++)
495     {
496       ids[i] = (Float.valueOf(alignment[i].getName().substring(8)))
497               .floatValue();
498     }
499
500     jalview.util.QuickSort.sort(ids, alignment);
501   }
502
503   /**
504    * Sort sequence in order of increasing score attribute for annotation with a
505    * particular scoreLabel. Or reverse if same label was used previously
506    * 
507    * @param scoreLabel
508    *          exact label for sequence associated AlignmentAnnotation scores to
509    *          use for sorting.
510    * @param alignment
511    *          sequences to be sorted
512    */
513   public static void sortByAnnotationScore(String scoreLabel,
514           AlignmentI alignment)
515   {
516     SequenceI[] seqs = alignment.getSequencesArray();
517     boolean[] hasScore = new boolean[seqs.length]; // per sequence score
518     // presence
519     int hasScores = 0; // number of scores present on set
520     double[] scores = new double[seqs.length];
521     double min = 0, max = 0;
522     for (int i = 0; i < seqs.length; i++)
523     {
524       AlignmentAnnotation[] scoreAnn = seqs[i].getAnnotation(scoreLabel);
525       if (scoreAnn != null)
526       {
527         hasScores++;
528         hasScore[i] = true;
529         scores[i] = scoreAnn[0].getScore(); // take the first instance of this
530         // score.
531         if (hasScores == 1)
532         {
533           max = min = scores[i];
534         }
535         else
536         {
537           if (max < scores[i])
538           {
539             max = scores[i];
540           }
541           if (min > scores[i])
542           {
543             min = scores[i];
544           }
545         }
546       }
547       else
548       {
549         hasScore[i] = false;
550       }
551     }
552     if (hasScores == 0)
553     {
554       return; // do nothing - no scores present to sort by.
555     }
556     if (hasScores < seqs.length)
557     {
558       for (int i = 0; i < seqs.length; i++)
559       {
560         if (!hasScore[i])
561         {
562           scores[i] = (max + i + 1.0);
563         }
564       }
565     }
566
567     jalview.util.QuickSort.sort(scores, seqs);
568
569     AlignmentSorter as = getInstance();
570
571     if (as.lastSortByAnnotation != scoreLabel)
572     {
573       as.lastSortByAnnotation = scoreLabel;
574       setOrder(alignment, seqs);
575     }
576     else
577     {
578       setReverseOrder(alignment, seqs);
579     }
580   }
581
582   /**
583    * Sort sequences by feature score or density, optionally restricted by
584    * feature types, feature groups, or alignment start/end positions.
585    * <p>
586    * If the sort is repeated for the same combination of types and groups, sort
587    * order is reversed.
588    * 
589    * @param featureTypes
590    *          a list of feature types to include (or null for all)
591    * @param groups
592    *          a list of feature groups to include (or null for all)
593    * @param startCol
594    *          start column position to include (base zero)
595    * @param endCol
596    *          end column position to include (base zero)
597    * @param alignment
598    *          the alignment to be sorted
599    * @param method
600    *          either "average_score" or "density" ("text" not yet implemented)
601    */
602   public static void sortByFeature(List<String> featureTypes,
603           List<String> groups, final int startCol, final int endCol,
604           AlignmentI alignment, String method)
605   {
606     if (method != FEATURE_SCORE && method != FEATURE_LABEL
607             && method != FEATURE_DENSITY)
608     {
609       String msg = String
610               .format("Implementation Error - sortByFeature method must be either '%s' or '%s'",
611                       FEATURE_SCORE, FEATURE_DENSITY);
612       System.err.println(msg);
613       return;
614     }
615
616     flipFeatureSortIfUnchanged(method, featureTypes, groups, startCol, endCol);
617
618     SequenceI[] seqs = alignment.getSequencesArray();
619
620     boolean[] hasScore = new boolean[seqs.length]; // per sequence score
621     // presence
622     int hasScores = 0; // number of scores present on set
623     double[] scores = new double[seqs.length];
624     int[] seqScores = new int[seqs.length];
625     Object[][] feats = new Object[seqs.length][];
626     double min = 0d;
627     double max = 0d;
628
629     for (int i = 0; i < seqs.length; i++)
630     {
631       /*
632        * get sequence residues overlapping column region
633        * and features for residue positions and specified types
634        */
635       String[] types = featureTypes == null ? null : featureTypes
636               .toArray(new String[featureTypes.size()]);
637       List<SequenceFeature> sfs = seqs[i].findFeatures(startCol + 1,
638               endCol + 1, types);
639
640       seqScores[i] = 0;
641       scores[i] = 0.0;
642
643       Iterator<SequenceFeature> it = sfs.listIterator();
644       while (it.hasNext())
645       {
646         SequenceFeature sf = it.next();
647
648         /*
649          * accept all features with null or empty group, otherwise
650          * check group is one of the currently visible groups
651          */
652         String featureGroup = sf.getFeatureGroup();
653         if (groups != null && featureGroup != null
654                 && !"".equals(featureGroup)
655                 && !groups.contains(featureGroup))
656         {
657           it.remove();
658         }
659         else
660         {
661           float score = sf.getScore();
662           if (FEATURE_SCORE.equals(method) && !Float.isNaN(score))
663           {
664             if (seqScores[i] == 0)
665             {
666               hasScores++;
667             }
668             seqScores[i]++;
669             hasScore[i] = true;
670             scores[i] += score;
671             // take the first instance of this score // ??
672           }
673         }
674       }
675
676       feats[i] = sfs.toArray(new SequenceFeature[sfs.size()]);
677       if (!sfs.isEmpty())
678       {
679         if (method == FEATURE_LABEL)
680         {
681           // order the labels by alphabet (not yet implemented)
682           String[] labs = new String[sfs.size()];
683           for (int l = 0; l < sfs.size(); l++)
684           {
685             SequenceFeature sf = sfs.get(l);
686             String description = sf.getDescription();
687             labs[l] = (description != null ? description : sf.getType());
688           }
689           QuickSort.sort(labs, feats[i]);
690         }
691       }
692       if (hasScore[i])
693       {
694         // compute average score
695         scores[i] /= seqScores[i];
696         // update the score bounds.
697         if (hasScores == 1)
698         {
699           min = scores[i];
700           max = min;
701         }
702         else
703         {
704           max = Math.max(max, scores[i]);
705           min = Math.min(min, scores[i]);
706         }
707       }
708     }
709
710     boolean doSort = false;
711
712     if (FEATURE_SCORE.equals(method))
713     {
714       if (hasScores == 0)
715       {
716         return; // do nothing - no scores present to sort by.
717       }
718       // pad score matrix
719       if (hasScores < seqs.length)
720       {
721         for (int i = 0; i < seqs.length; i++)
722         {
723           if (!hasScore[i])
724           {
725             scores[i] = (max + 1 + i);
726           }
727           else
728           {
729             // int nf = (feats[i] == null) ? 0
730             // : ((SequenceFeature[]) feats[i]).length;
731             // // System.err.println("Sorting on Score: seq " +
732             // seqs[i].getName()
733             // + " Feats: " + nf + " Score : " + scores[i]);
734           }
735         }
736       }
737       doSort = true;
738     }
739     else if (FEATURE_DENSITY.equals(method))
740     {
741       for (int i = 0; i < seqs.length; i++)
742       {
743         int featureCount = feats[i] == null ? 0
744                 : ((SequenceFeature[]) feats[i]).length;
745         scores[i] = featureCount;
746         // System.err.println("Sorting on Density: seq "+seqs[i].getName()+
747         // " Feats: "+featureCount+" Score : "+scores[i]);
748       }
749       doSort = true;
750     }
751     if (doSort)
752     {
753       QuickSort.sortByDouble(scores, seqs, getInstance().sortByFeatureAscending);
754     }
755     setOrder(alignment, seqs);
756   }
757
758   /**
759    * Builds a string hash of criteria for sorting, and if unchanged from last
760    * time, reverse the sort order
761    * 
762    * @param method
763    * @param featureTypes
764    * @param groups
765    * @param startCol
766    * @param endCol
767    */
768   protected static void flipFeatureSortIfUnchanged(String method,
769           List<String> featureTypes, List<String> groups,
770           final int startCol, final int endCol)
771   {
772     StringBuilder sb = new StringBuilder(64);
773     sb.append(startCol).append(method).append(endCol);
774     if (featureTypes != null)
775     {
776       Collections.sort(featureTypes);
777       sb.append(featureTypes.toString());
778     }
779     if (groups != null)
780     {
781       Collections.sort(groups);
782       sb.append(groups.toString());
783     }
784     String scoreCriteria = sb.toString();
785
786     /*
787      * if resorting on the same criteria, toggle sort order
788      */
789     AlignmentSorter as = getInstance();
790     if (as.sortByFeatureCriteria == null
791             || !scoreCriteria.equals(as.sortByFeatureCriteria))
792     {
793       as.sortByFeatureAscending = true;
794     }
795     else
796     {
797       as.sortByFeatureAscending = !as.sortByFeatureAscending;
798     }
799     as.sortByFeatureCriteria = scoreCriteria;
800   }
801
802   private static void sort(AlignmentI align, List<SequenceI> tmp,
803           boolean ascending)
804   {
805     sort(align, vectorSubsetToArray(tmp, align.getSequences()), ascending);
806   }
807
808   private static void sort(AlignmentI align, SequenceI[] seqs,
809           boolean ascending)
810   {
811     if (ascending)
812     {
813       setOrder(align, seqs);
814     }
815     else
816     {
817       setReverseOrder(align, seqs);
818     }
819
820   }
821
822
823   /**
824    * Sets the Alignment object with the given sequences
825    * 
826    * @param align
827    *          DOCUMENT ME!
828    * @param seqs
829    *          sequences as an array
830    */
831   public static void setOrder(AlignmentI align, SequenceI[] seqs)
832   {
833     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
834     List<SequenceI> algn = align.getSequences();
835     synchronized (algn)
836     {
837       List<SequenceI> tmp = new ArrayList<>();
838
839       for (int i = 0; i < seqs.length; i++)
840       {
841         if (algn.contains(seqs[i]))
842         {
843           tmp.add(seqs[i]);
844         }
845       }
846
847       algn.clear();
848       // User may have hidden seqs, then clicked undo or redo
849       for (int i = 0; i < tmp.size(); i++)
850       {
851         algn.add(tmp.get(i));
852       }
853     }
854   }
855
856   /**
857    * Reverse the order of the sort
858    * 
859    * @param align
860    *          DOCUMENT ME!
861    * @param seqs
862    *          DOCUMENT ME!
863    */
864   private static void setReverseOrder(AlignmentI align, SequenceI[] seqs)
865   {
866     int nSeq = seqs.length;
867
868     int len = (nSeq + (nSeq % 2)) / 2;
869 //    int len = 0;
870 //
871 //    if ((nSeq % 2) == 0)
872 //    {
873 //      len = nSeq / 2;
874 //    }
875 //    else
876 //    {
877 //      len = (nSeq + 1) / 2;
878 //    }
879
880     // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work
881     List<SequenceI> asq = align.getSequences();
882     synchronized (asq)
883     {
884       for (int i = 0; i < len; i++)
885       {
886         // SequenceI tmp = seqs[i];
887         asq.set(i, seqs[nSeq - i - 1]);
888         asq.set(nSeq - i - 1, seqs[i]);
889       }
890     }
891   }
892
893
894 }