+ /*
+ * fancy case - aligning via mappings between sequences
+ */
+ List<SequenceI> unmapped = new ArrayList<>();
+ Map<Integer, Map<SequenceI, Character>> columnMap = buildMappedColumnsMap(
+ unaligned, aligned, unmapped);
+ int width = columnMap.size();
+ char gap = unaligned.getGapCharacter();
+ int realignedCount = 0;
+ // TODO: verify this loop scales sensibly for very wide/high alignments
+
+ for (SequenceI seq : unaligned.getSequences())
+ {
+ if (!unmapped.contains(seq))
+ {
+ char[] newSeq = new char[width];
+ Arrays.fill(newSeq, gap); // JBPComment - doubt this is faster than the
+ // Integer iteration below
+ int newCol = 0;
+ int lastCol = 0;
+
+ /*
+ * traverse the map to find columns populated
+ * by our sequence
+ */
+ for (Integer column : columnMap.keySet())
+ {
+ Character c = columnMap.get(column).get(seq);
+ if (c != null)
+ {
+ /*
+ * sequence has a character at this position
+ *
+ */
+ newSeq[newCol] = c;
+ lastCol = newCol;
+ }
+ newCol++;
+ }
+
+ /*
+ * trim trailing gaps
+ */
+ if (lastCol < width)
+ {
+ char[] tmp = new char[lastCol + 1];
+ System.arraycopy(newSeq, 0, tmp, 0, lastCol + 1);
+ newSeq = tmp;
+ }
+ // TODO: optimise SequenceI to avoid char[]->String->char[]
+ seq.setSequence(String.valueOf(newSeq));
+ realignedCount++;
+ }
+ }
+ return realignedCount;
+ }
+
+ /**
+ * If unaligned and aligned sequences share the same dataset sequences, then
+ * simply copies the aligned sequences to the unaligned sequences and returns
+ * true; else returns false
+ *
+ * @param unaligned
+ * - sequences to be aligned based on aligned
+ * @param aligned
+ * - 'guide' alignment containing sequences derived from same dataset
+ * as unaligned
+ * @return
+ */
+ static boolean alignAsSameSequences(AlignmentI unaligned,
+ AlignmentI aligned)
+ {
+ if (aligned.getDataset() == null || unaligned.getDataset() == null)
+ {
+ return false; // should only pass alignments with datasets here
+ }
+
+ // map from dataset sequence to alignment sequence(s)
+ Map<SequenceI, List<SequenceI>> alignedDatasets = new HashMap<>();
+ for (SequenceI seq : aligned.getSequences())
+ {
+ SequenceI ds = seq.getDatasetSequence();
+ if (alignedDatasets.get(ds) == null)
+ {
+ alignedDatasets.put(ds, new ArrayList<SequenceI>());
+ }
+ alignedDatasets.get(ds).add(seq);
+ }
+
+ /*
+ * first pass - check whether all sequences to be aligned share a
+ * dataset sequence with an aligned sequence; also note the leftmost
+ * ungapped column from which to copy
+ */
+ int leftmost = Integer.MAX_VALUE;
+ for (SequenceI seq : unaligned.getSequences())
+ {
+ final SequenceI ds = seq.getDatasetSequence();
+ if (!alignedDatasets.containsKey(ds))
+ {
+ return false;
+ }
+ SequenceI alignedSeq = alignedDatasets.get(ds).get(0);
+ int startCol = alignedSeq.findIndex(seq.getStart()); // 1..
+ leftmost = Math.min(leftmost, startCol);
+ }
+
+ /*
+ * second pass - copy aligned sequences;
+ * heuristic rule: pair off sequences in order for the case where
+ * more than one shares the same dataset sequence
+ */
+ final char gapCharacter = aligned.getGapCharacter();
+ for (SequenceI seq : unaligned.getSequences())
+ {
+ List<SequenceI> alignedSequences = alignedDatasets
+ .get(seq.getDatasetSequence());
+ if (alignedSequences.isEmpty())
+ {
+ /*
+ * defensive check - shouldn't happen! (JAL-3536)
+ */
+ continue;
+ }
+ SequenceI alignedSeq = alignedSequences.get(0);
+
+ /*
+ * gap fill for leading (5') UTR if any
+ */
+ // TODO this copies intron columns - wrong!
+ int startCol = alignedSeq.findIndex(seq.getStart()); // 1..
+ int endCol = alignedSeq.findIndex(seq.getEnd());
+ char[] seqchars = new char[endCol - leftmost + 1];
+ Arrays.fill(seqchars, gapCharacter);
+ char[] toCopy = alignedSeq.getSequence(startCol - 1, endCol);
+ System.arraycopy(toCopy, 0, seqchars, startCol - leftmost,
+ toCopy.length);
+ seq.setSequence(String.valueOf(seqchars));
+ if (alignedSequences.size() > 0)
+ {
+ // pop off aligned sequences (except the last one)
+ alignedSequences.remove(0);
+ }
+ }
+
+ /*
+ * finally remove gapped columns (e.g. introns)
+ */
+ new RemoveGapColCommand("", unaligned.getSequencesArray(), 0,
+ unaligned.getWidth() - 1, unaligned);
+
+ return true;
+ }
+
+ /**
+ * Returns a map whose key is alignment column number (base 1), and whose
+ * values are a map of sequence characters in that column.
+ *
+ * @param unaligned
+ * @param aligned
+ * @param unmapped
+ * @return
+ */
+ static SortedMap<Integer, Map<SequenceI, Character>> buildMappedColumnsMap(
+ AlignmentI unaligned, AlignmentI aligned,
+ List<SequenceI> unmapped)
+ {
+ /*
+ * Map will hold, for each aligned column position, a map of
+ * {unalignedSequence, characterPerSequence} at that position.
+ * TreeMap keeps the entries in ascending column order.
+ */
+ SortedMap<Integer, Map<SequenceI, Character>> map = new TreeMap<>();
+
+ /*
+ * record any sequences that have no mapping so can't be realigned
+ */
+ unmapped.addAll(unaligned.getSequences());
+
+ List<AlignedCodonFrame> mappings = aligned.getCodonFrames();
+
+ for (SequenceI seq : unaligned.getSequences())
+ {
+ for (AlignedCodonFrame mapping : mappings)
+ {
+ SequenceI fromSeq = mapping.findAlignedSequence(seq, aligned);
+ if (fromSeq != null)
+ {
+ Mapping seqMap = mapping.getMappingBetween(fromSeq, seq);
+ if (addMappedPositions(seq, fromSeq, seqMap, map))
+ {
+ unmapped.remove(seq);
+ }
+ }
+ }
+ }
+ return map;