+ result.add(new int[] { begin, end });
+ }
+
+ /*
+ * Finally sort ranges by start position. This avoids a dependency on
+ * keeping features in order on the sequence (if they are in order anyway,
+ * the sort will have almost no work to do). The implicit assumption is CDS
+ * ranges are assembled in order. Other cases should not use this method,
+ * but instead construct an explicit mapping for CDS (e.g. EMBL parsing).
+ */
+ Collections.sort(result, IntRangeComparator.ASCENDING);
+ return result;
+ }
+
+ /**
+ * Makes an alignment with a copy of the given sequences, adding in any
+ * non-redundant sequences which are mapped to by the cross-referenced
+ * sequences.
+ *
+ * @param seqs
+ * @param xrefs
+ * @param dataset
+ * the alignment dataset shared by the new copy
+ * @return
+ */
+ public static AlignmentI makeCopyAlignment(SequenceI[] seqs,
+ SequenceI[] xrefs, AlignmentI dataset)
+ {
+ AlignmentI copy = new Alignment(new Alignment(seqs));
+ copy.setDataset(dataset);
+ boolean isProtein = !copy.isNucleotide();
+ SequenceIdMatcher matcher = new SequenceIdMatcher(seqs);
+ if (xrefs != null)
+ {
+ // BH 2019.01.25 recoded to remove iterators
+
+ for (int ix = 0, nx = xrefs.length; ix < nx; ix++)
+ {
+ SequenceI xref = xrefs[ix];
+ List<DBRefEntry> dbrefs = xref.getDBRefs();
+ if (dbrefs != null)
+ {
+ for (int ir = 0, nir = dbrefs.size(); ir < nir; ir++)
+ {
+ DBRefEntry dbref = dbrefs.get(ir);
+ Mapping map = dbref.getMap();
+ SequenceI mto;
+ if (map == null || (mto = map.getTo()) == null
+ || mto.isProtein() != isProtein)
+ {
+ continue;
+ }
+ SequenceI mappedTo = mto;
+ SequenceI match = matcher.findIdMatch(mappedTo);
+ if (match == null)
+ {
+ matcher.add(mappedTo);
+ copy.addSequence(mappedTo);
+ }
+ }
+ }
+ }
+ }
+ return copy;
+ }
+
+ /**
+ * Try to align sequences in 'unaligned' to match the alignment of their
+ * mapped regions in 'aligned'. For example, could use this to align CDS
+ * sequences which are mapped to their parent cDNA sequences.
+ *
+ * This method handles 1:1 mappings (dna-to-dna or protein-to-protein). For
+ * dna-to-protein or protein-to-dna use alternative methods.
+ *
+ * @param unaligned
+ * sequences to be aligned
+ * @param aligned
+ * holds aligned sequences and their mappings
+ * @return
+ */
+ public static int alignAs(AlignmentI unaligned, AlignmentI aligned)
+ {
+ /*
+ * easy case - aligning a copy of aligned sequences
+ */
+ if (alignAsSameSequences(unaligned, aligned))
+ {
+ return unaligned.getHeight();
+ }
+
+ /*
+ * 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);