+
+ /**
+ * types of feature ordering: Sort by score : average score - or total score -
+ * over all features in region Sort by feature label text: (or if null -
+ * feature type text) - numerical or alphabetical Sort by feature density:
+ * based on counts - ignoring individual text or scores for each feature
+ */
+ public static String FEATURE_SCORE = "average_score";
+
+ public static String FEATURE_LABEL = "text";
+
+ public static String FEATURE_DENSITY = "density";
+
+ /**
+ * sort the alignment using the features on each sequence found between start
+ * and stop with the given featureLabel (and optional group qualifier)
+ *
+ * @param featureLabel
+ * (may not be null)
+ * @param groupLabel
+ * (may be null)
+ * @param start
+ * (-1 to include non-positional features)
+ * @param stop
+ * (-1 to only sort on non-positional features)
+ * @param alignment
+ * - aligned sequences containing features
+ * @param method
+ * - one of the string constants FEATURE_SCORE, FEATURE_LABEL,
+ * FEATURE_DENSITY
+ */
+ public static void sortByFeature(String featureLabel, String groupLabel,
+ int start, int stop, AlignmentI alignment, String method)
+ {
+ sortByFeature(
+ featureLabel == null ? null : Arrays.asList(new String[]
+ { featureLabel }),
+ groupLabel == null ? null : Arrays.asList(new String[]
+ { groupLabel }), start, stop, alignment, method);
+ }
+
+ private static boolean containsIgnoreCase(final String lab,
+ final List<String> labs)
+ {
+ if (labs == null)
+ {
+ return true;
+ }
+ if (lab == null)
+ {
+ return false;
+ }
+ for (String label : labs)
+ {
+ if (lab.equalsIgnoreCase(label))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static void sortByFeature(List<String> featureLabels,
+ List<String> groupLabels, int start, int stop,
+ AlignmentI alignment, String method)
+ {
+ if (method != FEATURE_SCORE && method != FEATURE_LABEL
+ && method != FEATURE_DENSITY)
+ {
+ throw new Error(MessageManager
+ .getString("error.implementation_error_sortbyfeature"));
+ }
+
+ boolean ignoreScore = method != FEATURE_SCORE;
+ StringBuffer scoreLabel = new StringBuffer();
+ scoreLabel.append(start + stop + method);
+ // This doesn't quite work yet - we'd like to have a canonical ordering that
+ // can be preserved from call to call
+ if (featureLabels != null)
+ {
+ for (String label : featureLabels)
+ {
+ scoreLabel.append(label);
+ }
+ }
+ if (groupLabels != null)
+ {
+ for (String label : groupLabels)
+ {
+ scoreLabel.append(label);
+ }
+ }
+
+ /*
+ * if resorting the same feature, toggle sort order
+ */
+ if (lastSortByFeatureScore == null
+ || !scoreLabel.toString().equals(lastSortByFeatureScore))
+ {
+ sortByFeatureScoreAscending = true;
+ }
+ else
+ {
+ sortByFeatureScoreAscending = !sortByFeatureScoreAscending;
+ }
+ lastSortByFeatureScore = scoreLabel.toString();
+
+ SequenceI[] seqs = alignment.getSequencesArray();
+
+ boolean[] hasScore = new boolean[seqs.length]; // per sequence score
+ // presence
+ int hasScores = 0; // number of scores present on set
+ double[] scores = new double[seqs.length];
+ int[] seqScores = new int[seqs.length];
+ Object[] feats = new Object[seqs.length];
+ double min = 0, max = 0;
+ for (int i = 0; i < seqs.length; i++)
+ {
+ SequenceFeature[] sf = seqs[i].getSequenceFeatures();
+ if (sf == null)
+ {
+ sf = new SequenceFeature[0];
+ }
+ else
+ {
+ SequenceFeature[] tmp = new SequenceFeature[sf.length];
+ for (int s = 0; s < tmp.length; s++)
+ {
+ tmp[s] = sf[s];
+ }
+ sf = tmp;
+ }
+ int sstart = (start == -1) ? start : seqs[i].findPosition(start);
+ int sstop = (stop == -1) ? stop : seqs[i].findPosition(stop);
+ seqScores[i] = 0;
+ scores[i] = 0.0;
+ int n = sf.length;
+ for (int f = 0; f < sf.length; f++)
+ {
+ // filter for selection criteria
+ SequenceFeature feature = sf[f];
+
+ /*
+ * double-check feature overlaps columns (JAL-2544)
+ * (could avoid this with a findPositions(fromCol, toCol) method)
+ * findIndex returns base 1 column values, startCol/endCol are base 0
+ */
+ boolean noOverlap = seqs[i].findIndex(feature.getBegin()) > stop + 1
+ || seqs[i].findIndex(feature.getEnd()) < start + 1;
+ boolean skipFeatureType = featureLabels != null && !AlignmentSorter
+ .containsIgnoreCase(feature.type, featureLabels);
+ boolean skipFeatureGroup = groupLabels != null
+ && (feature.getFeatureGroup() != null
+ && !AlignmentSorter.containsIgnoreCase(
+ feature.getFeatureGroup(), groupLabels));
+ if (noOverlap || skipFeatureType || skipFeatureGroup)
+ {
+ // forget about this feature
+ sf[f] = null;
+ n--;
+ }
+ else
+ {
+ // or, also take a look at the scores if necessary.
+ if (!ignoreScore && !Float.isNaN(feature.getScore()))
+ {
+ if (seqScores[i] == 0)
+ {
+ hasScores++;
+ }
+ seqScores[i]++;
+ hasScore[i] = true;
+ scores[i] += feature.getScore(); // take the first instance of this
+ // score.
+ }
+ }
+ }
+ SequenceFeature[] fs;
+ feats[i] = fs = new SequenceFeature[n];
+ if (n > 0)
+ {
+ n = 0;
+ for (int f = 0; f < sf.length; f++)
+ {
+ if (sf[f] != null)
+ {
+ ((SequenceFeature[]) feats[i])[n++] = sf[f];
+ }
+ }
+ if (method == FEATURE_LABEL)
+ {
+ // order the labels by alphabet
+ String[] labs = new String[fs.length];
+ for (int l = 0; l < labs.length; l++)
+ {
+ labs[l] = (fs[l].getDescription() != null
+ ? fs[l].getDescription()
+ : fs[l].getType());
+ }
+ QuickSort.sort(labs, ((Object[]) feats[i]));
+ }
+ }
+ if (hasScore[i])
+ {
+ // compute average score
+ scores[i] /= seqScores[i];
+ // update the score bounds.
+ if (hasScores == 1)
+ {
+ max = min = scores[i];
+ }
+ else
+ {
+ if (max < scores[i])
+ {
+ max = scores[i];
+ }
+ if (min > scores[i])
+ {
+ min = scores[i];
+ }
+ }
+ }
+ }
+
+ if (method == FEATURE_SCORE)
+ {
+ if (hasScores == 0)
+ {
+ return; // do nothing - no scores present to sort by.
+ }
+ // pad score matrix
+ if (hasScores < seqs.length)
+ {
+ for (int i = 0; i < seqs.length; i++)
+ {
+ if (!hasScore[i])
+ {
+ scores[i] = (max + 1 + i);
+ }
+ else
+ {
+ // int nf = (feats[i] == null) ? 0
+ // : ((SequenceFeature[]) feats[i]).length;
+ // // System.err.println("Sorting on Score: seq " +
+ // seqs[i].getName()
+ // + " Feats: " + nf + " Score : " + scores[i]);
+ }
+ }
+ }
+ QuickSort.sortByDouble(scores, seqs, sortByFeatureScoreAscending);
+ }
+ else if (method == FEATURE_DENSITY)
+ {
+ for (int i = 0; i < seqs.length; i++)
+ {
+ int featureCount = feats[i] == null ? 0
+ : ((SequenceFeature[]) feats[i]).length;
+ scores[i] = featureCount;
+ // System.err.println("Sorting on Density: seq "+seqs[i].getName()+
+ // " Feats: "+featureCount+" Score : "+scores[i]);
+ }
+ QuickSort.sortByDouble(scores, seqs, sortByFeatureScoreAscending);
+ }
+ else
+ {
+ if (method == FEATURE_LABEL)
+ {
+ throw new Error(
+ MessageManager.getString("error.not_yet_implemented"));
+ }
+ }
+
+ setOrder(alignment, seqs);
+ }
+