2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
30 import jalview.analysis.AlignmentSorter;
31 import jalview.api.AlignViewportI;
32 import jalview.bin.Console;
33 import jalview.commands.CommandI;
34 import jalview.commands.EditCommand;
35 import jalview.commands.EditCommand.Action;
36 import jalview.commands.EditCommand.Edit;
37 import jalview.commands.OrderCommand;
38 import jalview.datamodel.AlignedCodonFrame;
39 import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
40 import jalview.datamodel.AlignmentI;
41 import jalview.datamodel.AlignmentOrder;
42 import jalview.datamodel.ColumnSelection;
43 import jalview.datamodel.HiddenColumns;
44 import jalview.datamodel.Mapping;
45 import jalview.datamodel.SearchResultMatchI;
46 import jalview.datamodel.SearchResults;
47 import jalview.datamodel.SearchResultsI;
48 import jalview.datamodel.Sequence;
49 import jalview.datamodel.SequenceGroup;
50 import jalview.datamodel.SequenceI;
53 * Helper methods for manipulations involving sequence mappings.
58 public final class MappingUtils
62 * Helper method to map a CUT or PASTE command.
65 * the original command
67 * if true, the command is to be undone
69 * the mapped sequences to apply the mapped command to
71 * the mapped EditCommand to add to
74 protected static void mapCutOrPaste(Edit edit, boolean undo,
75 List<SequenceI> targetSeqs, EditCommand result,
76 List<AlignedCodonFrame> mappings)
78 Action action = edit.getAction();
81 action = action.getUndoAction();
84 Console.error("MappingUtils.mapCutOrPaste not yet implemented");
88 * Returns a new EditCommand representing the given command as mapped to the
89 * given sequences. If there is no mapping, returns null.
98 public static EditCommand mapEditCommand(EditCommand command,
99 boolean undo, final AlignmentI mapTo, char gapChar,
100 List<AlignedCodonFrame> mappings)
103 * For now, only support mapping from protein edits to cDna
105 if (!mapTo.isNucleotide())
111 * Cache a copy of the target sequences so we can mimic successive edits on
112 * them. This lets us compute mappings for all edits in the set.
114 Map<SequenceI, SequenceI> targetCopies = new HashMap<>();
115 for (SequenceI seq : mapTo.getSequences())
117 SequenceI ds = seq.getDatasetSequence();
120 final SequenceI copy = new Sequence(seq);
121 copy.setDatasetSequence(ds);
122 targetCopies.put(ds, copy);
127 * Compute 'source' sequences as they were before applying edits:
129 Map<SequenceI, SequenceI> originalSequences = command.priorState(undo);
131 EditCommand result = new EditCommand();
132 Iterator<Edit> edits = command.getEditIterator(!undo);
133 while (edits.hasNext())
135 Edit edit = edits.next();
136 if (edit.getAction() == Action.CUT
137 || edit.getAction() == Action.PASTE)
139 mapCutOrPaste(edit, undo, mapTo.getSequences(), result, mappings);
141 else if (edit.getAction() == Action.INSERT_GAP
142 || edit.getAction() == Action.DELETE_GAP)
144 mapInsertOrDelete(edit, undo, originalSequences,
145 mapTo.getSequences(), targetCopies, gapChar, result,
149 return result.getSize() > 0 ? result : null;
153 * Helper method to map an edit command to insert or delete gaps.
156 * the original command
158 * if true, the action is to undo the command
159 * @param originalSequences
160 * the sequences the command acted on
162 * @param targetCopies
165 * the new EditCommand to add mapped commands to
168 protected static void mapInsertOrDelete(Edit edit, boolean undo,
169 Map<SequenceI, SequenceI> originalSequences,
170 final List<SequenceI> targetSeqs,
171 Map<SequenceI, SequenceI> targetCopies, char gapChar,
172 EditCommand result, List<AlignedCodonFrame> mappings)
174 Action action = edit.getAction();
177 * Invert sense of action if an Undo.
181 action = action.getUndoAction();
183 final int count = edit.getNumber();
184 final int editPos = edit.getPosition();
185 for (SequenceI seq : edit.getSequences())
188 * Get residue position at (or to right of) edit location. Note we use our
189 * 'copy' of the sequence before editing for this.
191 SequenceI ds = seq.getDatasetSequence();
196 final SequenceI actedOn = originalSequences.get(ds);
197 final int seqpos = actedOn.findPosition(editPos);
200 * Determine all mappings from this position to mapped sequences.
202 SearchResultsI sr = buildSearchResults(seq, seqpos, mappings);
206 for (SequenceI targetSeq : targetSeqs)
208 ds = targetSeq.getDatasetSequence();
213 SequenceI copyTarget = targetCopies.get(ds);
214 final int[] match = sr.getResults(copyTarget, 0,
215 copyTarget.getLength());
218 final int ratio = 3; // TODO: compute this - how?
219 final int mappedCount = count * ratio;
222 * Shift Delete start position left, as it acts on positions to its
225 int mappedEditPos = action == Action.DELETE_GAP
226 ? match[0] - mappedCount
228 Edit e = result.new Edit(action, new SequenceI[] { targetSeq },
229 mappedEditPos, mappedCount, gapChar);
233 * and 'apply' the edit to our copy of its target sequence
235 if (action == Action.INSERT_GAP)
237 copyTarget.setSequence(new String(
238 StringUtils.insertCharAt(copyTarget.getSequence(),
239 mappedEditPos, mappedCount, gapChar)));
241 else if (action == Action.DELETE_GAP)
243 copyTarget.setSequence(new String(
244 StringUtils.deleteChars(copyTarget.getSequence(),
245 mappedEditPos, mappedEditPos + mappedCount)));
251 * and 'apply' the edit to our copy of its source sequence
253 if (action == Action.INSERT_GAP)
255 actedOn.setSequence(new String(StringUtils.insertCharAt(
256 actedOn.getSequence(), editPos, count, gapChar)));
258 else if (action == Action.DELETE_GAP)
260 actedOn.setSequence(new String(StringUtils.deleteChars(
261 actedOn.getSequence(), editPos, editPos + count)));
267 * Returns a SearchResults object describing the mapped region corresponding
268 * to the specified sequence position.
275 public static SearchResultsI buildSearchResults(SequenceI seq, int index,
276 List<AlignedCodonFrame> seqmappings)
278 SearchResultsI results = new SearchResults();
279 addSearchResults(results, seq, index, seqmappings);
284 * Adds entries to a SearchResults object describing the mapped region
285 * corresponding to the specified sequence position.
292 public static void addSearchResults(SearchResultsI results, SequenceI seq,
293 int index, List<AlignedCodonFrame> seqmappings)
295 if (index >= seq.getStart() && index <= seq.getEnd())
297 for (AlignedCodonFrame acf : seqmappings)
299 acf.markMappedRegion(seq, index, results);
305 * Returns a (possibly empty) SequenceGroup containing any sequences in the
306 * mapped viewport corresponding to the given group in the source viewport.
313 public static SequenceGroup mapSequenceGroup(final SequenceGroup sg,
314 final AlignViewportI mapFrom, final AlignViewportI mapTo)
317 * Note the SequenceGroup holds aligned sequences, the mappings hold dataset
320 boolean targetIsNucleotide = mapTo.isNucleotide();
321 AlignViewportI protein = targetIsNucleotide ? mapFrom : mapTo;
322 List<AlignedCodonFrame> codonFrames = protein.getAlignment()
325 * Copy group name, colours etc, but not sequences or sequence colour scheme
327 SequenceGroup mappedGroup = new SequenceGroup(sg);
328 mappedGroup.setColourScheme(mapTo.getGlobalColourScheme());
331 int minStartCol = -1;
333 final int selectionStartRes = sg.getStartRes();
334 final int selectionEndRes = sg.getEndRes();
335 for (SequenceI selected : sg.getSequences())
338 * Find the widest range of non-gapped positions in the selection range
340 int firstUngappedPos = selectionStartRes;
341 while (firstUngappedPos <= selectionEndRes
342 && Comparison.isGap(selected.getCharAt(firstUngappedPos)))
348 * If this sequence is only gaps in the selected range, skip it
350 if (firstUngappedPos > selectionEndRes)
355 int lastUngappedPos = selectionEndRes;
356 while (lastUngappedPos >= selectionStartRes
357 && Comparison.isGap(selected.getCharAt(lastUngappedPos)))
363 * Find the selected start/end residue positions in sequence
365 int startResiduePos = selected.findPosition(firstUngappedPos);
366 int endResiduePos = selected.findPosition(lastUngappedPos);
367 for (SequenceI seq : mapTo.getAlignment().getSequences())
369 int mappedStartResidue = 0;
370 int mappedEndResidue = 0;
371 for (AlignedCodonFrame acf : codonFrames)
373 // rather than use acf.getCoveringMapping() we iterate through all
374 // mappings to make sure all CDS are selected for a protein
375 for (SequenceToSequenceMapping map : acf.getMappings())
377 if (map.covers(selected) && map.covers(seq))
380 * Found a sequence mapping. Locate the start/end mapped residues.
382 List<AlignedCodonFrame> mapping = Arrays
383 .asList(new AlignedCodonFrame[]
386 SearchResultsI sr = buildSearchResults(selected,
387 startResiduePos, mapping);
388 for (SearchResultMatchI m : sr.getResults())
390 mappedStartResidue = m.getStart();
391 mappedEndResidue = m.getEnd();
393 // locate end - allowing for adjustment of start range
394 sr = buildSearchResults(selected, endResiduePos, mapping);
395 for (SearchResultMatchI m : sr.getResults())
397 mappedStartResidue = Math.min(mappedStartResidue,
399 mappedEndResidue = Math.max(mappedEndResidue, m.getEnd());
403 * Find the mapped aligned columns, save the range. Note findIndex
404 * returns a base 1 position, SequenceGroup uses base 0
406 int mappedStartCol = seq.findIndex(mappedStartResidue) - 1;
407 minStartCol = minStartCol == -1 ? mappedStartCol
408 : Math.min(minStartCol, mappedStartCol);
409 int mappedEndCol = seq.findIndex(mappedEndResidue) - 1;
410 maxEndCol = maxEndCol == -1 ? mappedEndCol
411 : Math.max(maxEndCol, mappedEndCol);
412 mappedGroup.addSequence(seq, false);
419 mappedGroup.setStartRes(minStartCol < 0 ? 0 : minStartCol);
420 mappedGroup.setEndRes(maxEndCol < 0 ? 0 : maxEndCol);
425 * Returns an OrderCommand equivalent to the given one, but acting on mapped
426 * sequences as described by the mappings, or null if no mapping can be made.
429 * the original order command
431 * if true, the action is to undo the sort
433 * the alignment we are mapping to
435 * the mappings available
438 public static CommandI mapOrderCommand(OrderCommand command, boolean undo,
439 AlignmentI mapTo, List<AlignedCodonFrame> mappings)
441 SequenceI[] sortOrder = command.getSequenceOrder(undo);
442 List<SequenceI> mappedOrder = new ArrayList<>();
446 * Assumption: we are only interested in a cDNA/protein mapping; refactor in
447 * future if we want to support sorting (c)dna as (c)dna or protein as
450 boolean mappingToNucleotide = mapTo.isNucleotide();
451 for (SequenceI seq : sortOrder)
453 for (AlignedCodonFrame acf : mappings)
455 for (SequenceI seq2 : mapTo.getSequences())
458 * the corresponding peptide / CDS is the one for which there is
459 * a complete ('covering') mapping to 'seq'
461 SequenceI peptide = mappingToNucleotide ? seq2 : seq;
462 SequenceI cds = mappingToNucleotide ? seq : seq2;
463 SequenceToSequenceMapping s2s = acf.getCoveringMapping(cds,
467 mappedOrder.add(seq2);
476 * Return null if no mappings made.
484 * Add any unmapped sequences on the end of the sort in their original
487 if (j < mapTo.getHeight())
489 for (SequenceI seq : mapTo.getSequences())
491 if (!mappedOrder.contains(seq))
493 mappedOrder.add(seq);
499 * Have to sort the sequences before constructing the OrderCommand - which
500 * then resorts them?!?
502 final SequenceI[] mappedOrderArray = mappedOrder
503 .toArray(new SequenceI[mappedOrder.size()]);
504 SequenceI[] oldOrder = mapTo.getSequencesArray();
505 AlignmentSorter.sortBy(mapTo, new AlignmentOrder(mappedOrderArray));
506 final OrderCommand result = new OrderCommand(command.getDescription(),
512 * Returns a ColumnSelection in the 'mapTo' view which corresponds to the
513 * given selection in the 'mapFrom' view. We assume one is nucleotide, the
514 * other is protein (and holds the mappings from codons to protein residues).
521 public static void mapColumnSelection(ColumnSelection colsel,
522 HiddenColumns hiddencols, AlignViewportI mapFrom,
523 AlignViewportI mapTo, ColumnSelection newColSel,
524 HiddenColumns newHidden)
526 boolean targetIsNucleotide = mapTo.isNucleotide();
527 AlignViewportI protein = targetIsNucleotide ? mapFrom : mapTo;
528 List<AlignedCodonFrame> codonFrames = protein.getAlignment()
536 char fromGapChar = mapFrom.getAlignment().getGapCharacter();
539 * For each mapped column, find the range of columns that residues in that
542 List<SequenceI> fromSequences = mapFrom.getAlignment().getSequences();
543 List<SequenceI> toSequences = mapTo.getAlignment().getSequences();
545 for (Integer sel : colsel.getSelected())
547 mapColumn(sel.intValue(), codonFrames, newColSel, fromSequences,
548 toSequences, fromGapChar);
551 Iterator<int[]> regions = hiddencols.iterator();
552 while (regions.hasNext())
554 mapHiddenColumns(regions.next(), codonFrames, newHidden,
555 fromSequences, toSequences, fromGapChar);
561 * Helper method that maps a [start, end] hidden column range to its mapped
566 * @param mappedColumns
567 * @param fromSequences
571 protected static void mapHiddenColumns(int[] hidden,
572 List<AlignedCodonFrame> mappings, HiddenColumns mappedColumns,
573 List<SequenceI> fromSequences, List<SequenceI> toSequences,
576 for (int col = hidden[0]; col <= hidden[1]; col++)
578 int[] mappedTo = findMappedColumns(col, mappings, fromSequences,
579 toSequences, fromGapChar);
582 * Add the range of hidden columns to the mapped selection (converting
585 if (mappedTo != null)
587 mappedColumns.hideColumns(mappedTo[0] - 1, mappedTo[1] - 1);
593 * Helper method to map one column selection
596 * the column number (base 0)
598 * the sequence mappings
599 * @param mappedColumns
600 * the mapped column selections to add to
601 * @param fromSequences
605 protected static void mapColumn(int col, List<AlignedCodonFrame> mappings,
606 ColumnSelection mappedColumns, List<SequenceI> fromSequences,
607 List<SequenceI> toSequences, char fromGapChar)
609 int[] mappedTo = findMappedColumns(col, mappings, fromSequences,
610 toSequences, fromGapChar);
613 * Add the range of mapped columns to the mapped selection (converting
614 * base 1 to base 0). Note that this may include intron-only regions which
615 * lie between the start and end ranges of the selection.
617 if (mappedTo != null)
619 for (int i = mappedTo[0]; i <= mappedTo[1]; i++)
621 mappedColumns.addElement(i - 1);
627 * Helper method to find the range of columns mapped to from one column.
628 * Returns the maximal range of columns mapped to from all sequences in the
629 * source column, or null if no mappings were found.
633 * @param fromSequences
638 protected static int[] findMappedColumns(int col,
639 List<AlignedCodonFrame> mappings, List<SequenceI> fromSequences,
640 List<SequenceI> toSequences, char fromGapChar)
642 int[] mappedTo = new int[] { Integer.MAX_VALUE, Integer.MIN_VALUE };
643 boolean found = false;
646 * For each sequence in the 'from' alignment
648 for (SequenceI fromSeq : fromSequences)
651 * Ignore gaps (unmapped anyway)
653 if (fromSeq.getCharAt(col) == fromGapChar)
659 * Get the residue position and find the mapped position.
661 int residuePos = fromSeq.findPosition(col);
662 SearchResultsI sr = buildSearchResults(fromSeq, residuePos, mappings);
663 for (SearchResultMatchI m : sr.getResults())
665 int mappedStartResidue = m.getStart();
666 int mappedEndResidue = m.getEnd();
667 SequenceI mappedSeq = m.getSequence();
670 * Locate the aligned sequence whose dataset is mappedSeq. TODO a
671 * datamodel that can do this efficiently.
673 for (SequenceI toSeq : toSequences)
675 if (toSeq.getDatasetSequence() == mappedSeq
676 && mappedStartResidue >= toSeq.getStart()
677 && mappedEndResidue <= toSeq.getEnd())
679 int mappedStartCol = toSeq.findIndex(mappedStartResidue);
680 int mappedEndCol = toSeq.findIndex(mappedEndResidue);
681 mappedTo[0] = Math.min(mappedTo[0], mappedStartCol);
682 mappedTo[1] = Math.max(mappedTo[1], mappedEndCol);
685 // note: remove break if we ever want to map one to many sequences
690 return found ? mappedTo : null;
694 * Returns the mapped codon or codons for a given aligned sequence column
698 * an aligned peptide sequence
700 * an aligned column position (base 0)
702 * a set of codon mappings
703 * @return the bases of the mapped codon(s) in the cDNA dataset sequence(s),
704 * or an empty list if none found
706 public static List<char[]> findCodonsFor(SequenceI seq, int col,
707 List<AlignedCodonFrame> mappings)
709 List<char[]> result = new ArrayList<>();
710 int dsPos = seq.findPosition(col);
711 for (AlignedCodonFrame mapping : mappings)
713 if (mapping.involvesSequence(seq))
715 List<char[]> codons = mapping
716 .getMappedCodons(seq.getDatasetSequence(), dsPos);
719 result.addAll(codons);
727 * Converts a series of [start, end] range pairs into an array of individual
728 * positions. This also caters for 'reverse strand' (start > end) cases.
733 public static int[] flattenRanges(int[] ranges)
736 * Count how many positions altogether
739 for (int i = 0; i < ranges.length - 1; i += 2)
741 count += Math.abs(ranges[i + 1] - ranges[i]) + 1;
744 int[] result = new int[count];
746 for (int i = 0; i < ranges.length - 1; i += 2)
748 int from = ranges[i];
749 final int to = ranges[i + 1];
750 int step = from <= to ? 1 : -1;
755 } while (from != to + step);
761 * Returns a list of any mappings that are from or to the given (aligned or
768 public static List<AlignedCodonFrame> findMappingsForSequence(
769 SequenceI sequence, List<AlignedCodonFrame> mappings)
771 return findMappingsForSequenceAndOthers(sequence, mappings, null);
775 * Returns a list of any mappings that are from or to the given (aligned or
776 * dataset) sequence, optionally limited to mappings involving one of a given
784 public static List<AlignedCodonFrame> findMappingsForSequenceAndOthers(
785 SequenceI sequence, List<AlignedCodonFrame> mappings,
786 List<SequenceI> filterList)
788 List<AlignedCodonFrame> result = new ArrayList<>();
789 if (sequence == null || mappings == null)
793 for (AlignedCodonFrame mapping : mappings)
795 if (mapping.involvesSequence(sequence))
797 if (filterList != null)
799 for (SequenceI otherseq : filterList)
801 SequenceI otherDataset = otherseq.getDatasetSequence();
802 if (otherseq == sequence
803 || otherseq == sequence.getDatasetSequence()
804 || (otherDataset != null && (otherDataset == sequence
805 || otherDataset == sequence
806 .getDatasetSequence())))
808 // skip sequences in subset which directly relate to sequence
811 if (mapping.involvesSequence(otherseq))
813 // selected a mapping contained in subselect alignment
829 * Returns the total length of the supplied ranges, which may be as single
830 * [start, end] or multiple [start, end, start, end ...]
835 public static int getLength(List<int[]> ranges)
842 for (int[] range : ranges)
844 if (range.length % 2 != 0)
847 "Error unbalance start/end ranges: " + ranges.toString());
850 for (int i = 0; i < range.length - 1; i += 2)
852 length += Math.abs(range[i + 1] - range[i]) + 1;
859 * Answers true if any range includes the given value
865 public static boolean contains(List<int[]> ranges, int value)
871 for (int[] range : ranges)
873 if (range[1] >= range[0] && value >= range[0] && value <= range[1])
876 * value within ascending range
880 if (range[1] < range[0] && value <= range[0] && value >= range[1])
883 * value within descending range
892 * Removes a specified number of positions from the start of a ranges list.
893 * For example, could be used to adjust cds ranges to allow for an incomplete
894 * start codon. Subranges are removed completely, or their start positions
895 * adjusted, until the required number of positions has been removed from the
896 * range. Reverse strand ranges are supported. The input array is not
901 * an array of [start, end, start, end...] positions
902 * @return a new array with the first removeCount positions removed
904 public static int[] removeStartPositions(int removeCount,
907 if (removeCount <= 0)
912 int[] copy = Arrays.copyOf(ranges, ranges.length);
915 for (int x = 0; x < copy.length && sxpos == -1; x += 2)
917 cdspos += Math.abs(copy[x + 1] - copy[x]) + 1;
918 if (removeCount < cdspos)
921 * we have removed enough, time to finish
926 * increment start of first exon, or decrement if reverse strand
928 if (copy[x] <= copy[x + 1])
930 copy[x] = copy[x + 1] - cdspos + removeCount + 1;
934 copy[x] = copy[x + 1] + cdspos - removeCount - 1;
943 * we dropped at least one entire sub-range - compact the array
945 int[] nxon = new int[copy.length - sxpos];
946 System.arraycopy(copy, sxpos, nxon, 0, copy.length - sxpos);
953 * Answers true if range's start-end positions include those of queryRange,
954 * where either range might be in reverse direction, else false
959 * a candidate subrange of range (start2-end2)
962 public static boolean rangeContains(int[] range, int[] queryRange)
964 if (range == null || queryRange == null || range.length != 2
965 || queryRange.length != 2)
973 int min = Math.min(range[0], range[1]);
974 int max = Math.max(range[0], range[1]);
976 return (min <= queryRange[0] && max >= queryRange[0]
977 && min <= queryRange[1] && max >= queryRange[1]);
981 * Removes the specified number of positions from the given ranges. Provided
982 * to allow a stop codon to be stripped from a CDS sequence so that it matches
983 * the peptide translation length.
987 * a list of (single) [start, end] ranges
990 public static void removeEndPositions(int positions, List<int[]> ranges)
992 int toRemove = positions;
993 Iterator<int[]> it = new ReverseListIterator<>(ranges);
996 int[] endRange = it.next();
997 if (endRange.length != 2)
1000 * not coded for [start1, end1, start2, end2, ...]
1003 "MappingUtils.removeEndPositions doesn't handle multiple ranges");
1007 int length = endRange[1] - endRange[0] + 1;
1011 * not coded for a reverse strand range (end < start)
1014 "MappingUtils.removeEndPositions doesn't handle reverse strand");
1017 if (length > toRemove)
1019 endRange[1] -= toRemove;
1031 * Converts a list of {@code start-end} ranges to a single array of
1032 * {@code start1, end1, start2, ... } ranges
1037 public static int[] rangeListToArray(List<int[]> ranges)
1039 int rangeCount = ranges.size();
1040 int[] result = new int[rangeCount * 2];
1042 for (int i = 0; i < rangeCount; i++)
1044 int[] range = ranges.get(i);
1045 result[j++] = range[0];
1046 result[j++] = range[1];
1052 * Returns the maximal start-end positions in the given (ordered) list of
1053 * ranges which is overlapped by the given begin-end range, or null if there
1058 * if ranges is {[4, 8], [10, 12], [16, 19]}
1060 * findOverlap(ranges, 1, 20) == [4, 19]
1061 * findOverlap(ranges, 6, 11) == [6, 11]
1062 * findOverlap(ranges, 9, 15) == [10, 12]
1063 * findOverlap(ranges, 13, 15) == null
1071 protected static int[] findOverlap(List<int[]> ranges, final int begin,
1074 boolean foundStart = false;
1079 * traverse the ranges to find the first position (if any) >= begin,
1080 * and the last position (if any) <= end
1082 for (int[] range : ranges)
1086 if (range[0] >= begin)
1089 * first range that starts with, or follows, begin
1092 from = Math.max(range[0], begin);
1094 else if (range[1] >= begin)
1097 * first range that contains begin
1104 if (range[0] <= end)
1106 to = Math.min(end, range[1]);
1110 return foundStart && to >= from ? new int[] { from, to } : null;