+ ranges.add(iv); // add only - or final range
+ }
+ else
+ {
+ // walk from end of interval.
+ i = shiftFrom.size() - 1;
+ while (i > fs)
+ {
+ i--;
+ }
+ iv = shiftFrom.get(i);
+ iv = new int[] { iv[1], iv[0] };// reverse and clone
+ // truncate initial interval
+ if (i == fs)
+ {
+ iv[0] = startpos;
+ }
+ while (--i != fe)
+ { // fix apparent logic bug when fe==-1
+ ranges.add(iv); // add (truncated) reversed interval
+ iv = shiftFrom.get(i);
+ iv = new int[] { iv[1], iv[0] }; // reverse and clone
+ }
+ if (i == fe)
+ {
+ // interval is already reversed
+ iv[1] = endpos;
+ }
+ ranges.add(iv); // add only - or final range
+ }
+ // create array of start end intervals.
+ int[] range = null;
+ if (ranges != null && ranges.size() > 0)
+ {
+ range = new int[ranges.size() * 2];
+ intv = 0;
+ intvSize = ranges.size();
+ i = 0;
+ while (intv < intvSize)
+ {
+ iv = ranges.get(intv);
+ range[i++] = iv[0];
+ range[i++] = iv[1];
+ ranges.set(intv++, null); // remove
+ }
+ }
+ return range;
+ }
+
+ /**
+ * get the 'initial' position of mpos in To
+ *
+ * @param mpos
+ * position in from
+ * @return position of first word in to reference frame
+ */
+ public int getToPosition(int mpos)
+ {
+ // TODO not used - remove??
+ int[] mp = shiftTo(mpos);
+ if (mp != null)
+ {
+ return mp[0];
+ }
+ return mpos;
+ }
+
+ /**
+ * get range of positions in To frame for the mpos word in From
+ *
+ * @param mpos
+ * position in From
+ * @return null or int[] first position in To for mpos, last position in to
+ * for Mpos
+ */
+ public int[] getToWord(int mpos)
+ {
+ int[] mp = shiftTo(mpos);
+ if (mp != null)
+ {
+ return new int[] { mp[0], mp[0] + mp[2] * (getFromRatio() - 1) };
+ }
+ return null;
+ }
+
+ /**
+ * get From position in the associated reference frame for position pos in the
+ * associated sequence.
+ *
+ * @param pos
+ * @return
+ */
+ public int getMappedPosition(int pos)
+ {
+ // TODO not used - remove??
+ int[] mp = shiftFrom(pos);
+ if (mp != null)
+ {
+ return mp[0];
+ }
+ return pos;
+ }
+
+ public int[] getMappedWord(int pos)
+ {
+ // TODO not used - remove??
+ int[] mp = shiftFrom(pos);
+ if (mp != null)
+ {
+ return new int[] { mp[0], mp[0] + mp[2] * (getToRatio() - 1) };
+ }
+ return null;
+ }
+
+ /**
+ *
+ * @return a MapList whose From range is this maplist's To Range, and vice
+ * versa
+ */
+ public MapList getInverse()
+ {
+ return new MapList(getToRanges(), getFromRanges(), getToRatio(),
+ getFromRatio());
+ }
+
+ /**
+ * test for containment rather than equivalence to another mapping
+ *
+ * @param map
+ * to be tested for containment
+ * @return true if local or mapped range map contains or is contained by this
+ * mapping
+ */
+ public boolean containsEither(boolean local, MapList map)
+ {
+ // TODO not used - remove?
+ if (local)
+ {
+ return ((getFromLowest() >= map.getFromLowest()
+ && getFromHighest() <= map.getFromHighest())
+ || (getFromLowest() <= map.getFromLowest()
+ && getFromHighest() >= map.getFromHighest()));
+ }
+ else
+ {
+ return ((getToLowest() >= map.getToLowest()
+ && getToHighest() <= map.getToHighest())
+ || (getToLowest() <= map.getToLowest()
+ && getToHighest() >= map.getToHighest()));
+ }
+ }
+
+ /**
+ * String representation - for debugging, not guaranteed not to change
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("[");
+ for (int[] shift : fromShifts)
+ {
+ sb.append(" ").append(Arrays.toString(shift));
+ }
+ sb.append(" ] ");
+ sb.append(fromRatio).append(":").append(toRatio);
+ sb.append(" to [");
+ for (int[] shift : toShifts)
+ {
+ sb.append(" ").append(Arrays.toString(shift));
+ }
+ sb.append(" ]");
+ return sb.toString();
+ }
+
+ /**
+ * Extend this map list by adding the given map's ranges. There is no
+ * validation check that the ranges do not overlap existing ranges (or each
+ * other), but contiguous ranges are merged.
+ *
+ * @param map
+ */
+ public void addMapList(MapList map)
+ {
+ if (this.equals(map))
+ {
+ return;
+ }
+ this.fromLowest = Math.min(fromLowest, map.fromLowest);
+ this.toLowest = Math.min(toLowest, map.toLowest);
+ this.fromHighest = Math.max(fromHighest, map.fromHighest);
+ this.toHighest = Math.max(toHighest, map.toHighest);
+
+ for (int[] range : map.getFromRanges())
+ {
+ addRange(range, fromShifts);
+ }
+ for (int[] range : map.getToRanges())
+ {
+ addRange(range, toShifts);
+ }
+ }
+
+ /**
+ * Adds the given range to a list of ranges. If the new range just extends
+ * existing ranges, the current endpoint is updated instead.
+ *
+ * @param range
+ * @param addTo
+ */
+ static void addRange(int[] range, List<int[]> addTo)
+ {
+ /*
+ * list is empty - add to it!
+ */
+ if (addTo.size() == 0)
+ {
+ addTo.add(range);
+ return;
+ }
+
+ int[] last = addTo.get(addTo.size() - 1);
+ boolean lastForward = last[1] >= last[0];
+ boolean newForward = range[1] >= range[0];
+
+ /*
+ * contiguous range in the same direction - just update endpoint
+ */
+ if (lastForward == newForward && last[1] == range[0])
+ {
+ last[1] = range[1];
+ return;
+ }
+
+ /*
+ * next range starts at +1 in forward sense - update endpoint
+ */
+ if (lastForward && newForward && range[0] == last[1] + 1)
+ {
+ last[1] = range[1];
+ return;
+ }
+
+ /*
+ * next range starts at -1 in reverse sense - update endpoint
+ */
+ if (!lastForward && !newForward && range[0] == last[1] - 1)
+ {
+ last[1] = range[1];
+ return;
+ }
+
+ /*
+ * just add the new range
+ */
+ addTo.add(range);
+ }
+
+ /**
+ * Returns true if mapping is from forward strand, false if from reverse
+ * strand. Result is just based on the first 'from' range that is not a single
+ * position. Default is true unless proven to be false. Behaviour is not well
+ * defined if the mapping has a mixture of forward and reverse ranges.
+ *
+ * @return
+ */
+ public boolean isFromForwardStrand()
+ {
+ return isForwardStrand(getFromRanges());
+ }
+
+ /**
+ * Returns true if mapping is to forward strand, false if to reverse strand.
+ * Result is just based on the first 'to' range that is not a single position.
+ * Default is true unless proven to be false. Behaviour is not well defined if
+ * the mapping has a mixture of forward and reverse ranges.
+ *
+ * @return
+ */
+ public boolean isToForwardStrand()
+ {
+ return isForwardStrand(getToRanges());
+ }
+
+ /**
+ * A helper method that returns true unless at least one range has start >
+ * end. Behaviour is undefined for a mixture of forward and reverse ranges.
+ *
+ * @param ranges
+ * @return
+ */
+ private boolean isForwardStrand(List<int[]> ranges)
+ {
+ boolean forwardStrand = true;
+ for (int[] range : ranges)
+ {
+ if (range[1] > range[0])
+ {
+ break; // forward strand confirmed
+ }
+ else if (range[1] < range[0])