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()
{
boolean forwardStrand = true;
for (int[] range : getFromRanges())
{
if (range[1] > range[0])
{
break; // forward strand confirmed
}
else if (range[1] < range[0])
{
forwardStrand = false;
break; // reverse strand confirmed
}
}
return forwardStrand;
}
/**
*
* @return true if from, or to is a three to 1 mapping
*/
public boolean isTripletMap()
{
return (toRatio == 3 && fromRatio == 1)
|| (fromRatio == 3 && toRatio == 1);
}
/**
* Returns a map which is the composite of this one and the input map. That
* is, the output map has the fromRanges of this map, and its toRanges are the
* toRanges of this map as transformed by the input map.
*
* Returns null if the mappings cannot be traversed (not all toRanges of this
* map correspond to fromRanges of the input), or if this.toRatio does not
* match map.fromRatio.
*
*
* Example 1:
* this: from [1-100] to [501-600]
* input: from [10-40] to [60-90]
* output: from [10-40] to [560-590]
* Example 2 ('reverse strand exons'):
* this: from [1-100] to [2000-1951], [1000-951] // transcript to loci
* input: from [1-50] to [41-90] // CDS to transcript
* output: from [10-40] to [1960-1951], [1000-971] // CDS to gene loci
*
*
* @param map
* @return
*/
public MapList traverse(MapList map)
{
if (map == null)
{
return null;
}
/*
* compound the ratios by this rule:
* A:B with M:N gives A*M:B*N
* reduced by greatest common divisor
* so 1:3 with 3:3 is 3:9 or 1:3
* 1:3 with 3:1 is 3:3 or 1:1
* 1:3 with 1:3 is 1:9
* 2:5 with 3:7 is 6:35
*/
int outFromRatio = getFromRatio() * map.getFromRatio();
int outToRatio = getToRatio() * map.getToRatio();
int gcd = MathUtils.gcd(outFromRatio, outToRatio);
outFromRatio /= gcd;
outToRatio /= gcd;
List toRanges = new ArrayList<>();
for (int[] range : getToRanges())
{
int[] transferred = map.locateInTo(range[0], range[1]);
if (transferred == null)
{
return null;
}
toRanges.add(transferred);
}
return new MapList(getFromRanges(), toRanges, outFromRatio, outToRatio);
}
}