+ /**
+ * A local iterator which reverse iterates over hidden column regions in a
+ * range. Intended for use ONLY within the HiddenColumns class, because it
+ * works directly with the hiddenColumns collection without locking (callers
+ * should lock hiddenColumns).
+ */
+ private class ReverseRegionsIterator implements Iterator<int[]>
+ {
+ // start position to iterate to
+ private int start;
+
+ // end position to iterate from
+ private int end;
+
+ // current index in hiddenColumns
+ private int currentPosition = 0;
+
+ // current column in hiddenColumns
+ private int[] nextRegion = null;
+
+ // Constructor with bounds
+ ReverseRegionsIterator(int lowerBound, int upperBound)
+ {
+ init(lowerBound, upperBound);
+ }
+
+ /**
+ * Construct an iterator over hiddenColums bounded at
+ * [lowerBound,upperBound]
+ *
+ * @param lowerBound
+ * lower bound to iterate to
+ * @param upperBound
+ * upper bound to iterate from
+ */
+ private void init(int lowerBound, int upperBound)
+ {
+ start = lowerBound;
+ end = upperBound;
+
+ if (hiddenColumns != null)
+ {
+ // iterate until a region overlaps with [start,end]
+ currentPosition = hiddenColumns.size() - 1;
+ while (currentPosition >= 0
+ && hiddenColumns.get(currentPosition)[1] > end)
+ {
+ currentPosition--;
+ }
+ if (currentPosition >= 0)
+ {
+ nextRegion = hiddenColumns.get(currentPosition);
+ }
+ }
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return (hiddenColumns != null) && (nextRegion != null)
+ && (nextRegion[1] >= start);
+ }
+
+ @Override
+ public int[] next()
+ {
+ int[] region = nextRegion;
+ currentPosition--;
+ if (currentPosition >= 0)
+ {
+ nextRegion = hiddenColumns.get(currentPosition);
+ }
+ else
+ {
+ nextRegion = null;
+ }
+ return region;
+ }
+
+ }
+
+ /**
+ * An iterator which iterates over hidden column regions in a range. Works
+ * with a copy of the hidden columns collection. Intended to be used by
+ * callers OUTSIDE of HiddenColumns.
+ */
+ private class BoundedHiddenColsIterator implements Iterator<int[]>
+ {
+ // start position to iterate from
+ private int start;
+
+ // end position to iterate to
+ private int end;
+
+ // current index in hiddenColumns
+ private int currentPosition = 0;
+
+ // current column in hiddenColumns
+ private int[] currentRegion;
+
+ // local copy or reference to hiddenColumns
+ private List<int[]> localHidden;
+
+ /**
+ * Unbounded constructor
+ */
+ BoundedHiddenColsIterator()
+ {
+ if (hiddenColumns != null)
+ {
+ int last = hiddenColumns.get(hiddenColumns.size() - 1)[1];
+ init(0, last);
+ }
+ else
+ {
+ init(0, 0);
+ }
+ }
+
+ /**
+ * Construct an iterator over hiddenColums bounded at
+ * [lowerBound,upperBound]
+ *
+ * @param lowerBound
+ * lower bound to iterate from
+ * @param upperBound
+ * upper bound to iterate to
+ */
+ BoundedHiddenColsIterator(int lowerBound, int upperBound)
+ {
+ init(lowerBound, upperBound);
+ }
+
+ /**
+ * Construct an iterator over hiddenColums bounded at
+ * [lowerBound,upperBound]
+ *
+ * @param lowerBound
+ * lower bound to iterate from
+ * @param upperBound
+ * upper bound to iterate to
+ */
+ private void init(int lowerBound, int upperBound)
+ {
+ start = lowerBound;
+ end = upperBound;
+
+ try
+ {
+ LOCK.readLock().lock();
+
+ if (hiddenColumns != null)
+ {
+ localHidden = new ArrayList<>();
+
+ // iterate until a region overlaps with [start,end]
+ int i = 0;
+ while ((i < hiddenColumns.size())
+ && (hiddenColumns.get(i)[1] < start))
+ {
+ i++;
+ }
+
+ // iterate from start to end, adding each hidden region. Positions are
+ // absolute, and all regions which *overlap* [start,end] are added.
+ while (i < hiddenColumns.size()
+ && (hiddenColumns.get(i)[0] <= end))
+ {
+ int[] rh = hiddenColumns.get(i);
+ int[] cp = new int[2];
+ System.arraycopy(rh, 0, cp, 0, rh.length);
+ localHidden.add(cp);
+ i++;
+ }
+ }
+ }
+ finally
+ {
+ LOCK.readLock().unlock();
+ }
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return (localHidden != null)
+ && (currentPosition < localHidden.size());
+ }
+
+ @Override
+ public int[] next()
+ {
+ currentRegion = localHidden.get(currentPosition);
+ currentPosition++;
+ return currentRegion;
+ }
+ }
+
+ /**
+ * An iterator which iterates over visible start positions of hidden column
+ * regions in a range.
+ */
+ private class BoundedStartRegionIterator implements Iterator<Integer>
+ {
+ // start position to iterate from
+ private int start;
+
+ // end position to iterate to
+ private int end;
+
+ // current index in hiddenColumns
+ private int currentPosition = 0;
+
+ // local copy or reference to hiddenColumns
+ private List<Integer> positions = null;
+
+ /**
+ * Construct an iterator over hiddenColums bounded at
+ * [lowerBound,upperBound]
+ *
+ * @param lowerBound
+ * lower bound to iterate from
+ * @param upperBound
+ * upper bound to iterate to
+ * @param useCopyCols
+ * whether to make a local copy of hiddenColumns for iteration (set
+ * to true if calling from outwith the HiddenColumns class)
+ */
+ BoundedStartRegionIterator(int lowerBound, int upperBound,
+ boolean useCopy)
+ {
+ start = lowerBound;
+ end = upperBound;
+
+ try
+ {
+ if (useCopy)
+ {
+ // assume that if useCopy is false the calling code has locked
+ // hiddenColumns
+ LOCK.readLock().lock();
+ }
+
+ if (hiddenColumns != null)
+ {
+ positions = new ArrayList<>(hiddenColumns.size());
+
+ // navigate to start, keeping count of hidden columns
+ int i = 0;
+ int hiddenSoFar = 0;
+ while ((i < hiddenColumns.size())
+ && (hiddenColumns.get(i)[0] < start + hiddenSoFar))
+ {
+ int[] region = hiddenColumns.get(i);
+ hiddenSoFar += region[1] - region[0] + 1;
+ i++;
+ }
+
+ // iterate from start to end, adding start positions of each
+ // hidden region. Positions are visible columns count, not absolute
+ while (i < hiddenColumns.size()
+ && (hiddenColumns.get(i)[0] <= end + hiddenSoFar))
+ {
+ int[] region = hiddenColumns.get(i);
+ positions.add(region[0] - hiddenSoFar);
+ hiddenSoFar += region[1] - region[0] + 1;
+ i++;
+ }
+ }
+ else
+ {
+ positions = new ArrayList<>();
+ }
+ } finally
+ {
+ if (useCopy)
+ {
+ LOCK.readLock().unlock();
+ }
+ }
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return (currentPosition < positions.size());
+ }
+
+ /**
+ * Get next hidden region start position
+ *
+ * @return the start position in *visible* coordinates
+ */
+ @Override
+ public Integer next()
+ {
+ int result = positions.get(currentPosition);
+ currentPosition++;
+ return result;
+ }
+ }
+
+ /**
+ * Iterator over the visible *columns* (not regions) as determined by the set
+ * of hidden columns. Uses a local copy of hidden columns.
+ *
+ * @author kmourao
+ *
+ */
+ private class VisibleColsIterator implements Iterator<Integer>
+ {
+ private int last;
+
+ private int current;
+
+ private int next;
+
+ private List<int[]> localHidden = new ArrayList<>();
+
+ private int nexthiddenregion;
+
+ VisibleColsIterator(int firstcol, int lastcol)
+ {
+ last = lastcol;
+ current = firstcol;
+ next = firstcol;
+ nexthiddenregion = 0;
+
+ LOCK.readLock().lock();
+
+ if (hiddenColumns != null)
+ {
+ int i = 0;
+ for (i = 0; i < hiddenColumns.size()
+ && (current <= hiddenColumns.get(i)[0]); ++i)
+ {
+ if (current >= hiddenColumns.get(i)[0]
+ && current <= hiddenColumns.get(i)[1])
+ {
+ // current is hidden, move to right
+ current = hiddenColumns.get(i)[1] + 1;
+ next = current;
+ nexthiddenregion = i + 1;
+ }
+ }
+
+ for (i = hiddenColumns.size() - 1; i >= 0
+ && (last >= hiddenColumns.get(i)[1]); --i)
+ {
+ if (last >= hiddenColumns.get(i)[0]
+ && last <= hiddenColumns.get(i)[1])
+ {
+ // last is hidden, move to left
+ last = hiddenColumns.get(i)[0] - 1;
+ }
+ }
+
+ // make a local copy of the bit we need
+ i = nexthiddenregion;
+ while (i < hiddenColumns.size() && hiddenColumns.get(i)[0] <= last)
+ {
+ int[] region = new int[] { hiddenColumns.get(i)[0],
+ hiddenColumns.get(i)[1] };
+ localHidden.add(region);
+ i++;
+ }
+ }
+
+ LOCK.readLock().unlock();
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return next <= last;
+ }
+
+ @Override
+ public Integer next()
+ {
+ if (next > last)
+ {
+ throw new NoSuchElementException();
+ }
+ current = next;
+ if ((localHidden != null)
+ && (nexthiddenregion < localHidden.size()))
+ {
+ // still some more hidden regions
+ if (next + 1 < localHidden.get(nexthiddenregion)[0])
+ {
+ // next+1 is still before the next hidden region
+ next++;
+ }
+ else if ((next + 1 >= localHidden.get(nexthiddenregion)[0])
+ && (next + 1 <= localHidden.get(nexthiddenregion)[1]))
+ {
+ // next + 1 is in the next hidden region
+ next = localHidden.get(nexthiddenregion)[1] + 1;
+ nexthiddenregion++;
+ }
+ }
+ else
+ {
+ // finished with hidden regions, just increment normally
+ next++;
+ }
+ return current;
+ }
+
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * An iterator which iterates over visible regions in a range.
+ */
+ private class VisibleContigsIterator implements Iterator<int[]>
+ {
+ private List<int[]> vcontigs = new ArrayList<>();
+
+ private int currentPosition = 0;
+
+ VisibleContigsIterator(int start, int end, boolean usecopy)
+ {
+ try
+ {
+ if (usecopy)
+ {
+ LOCK.readLock().lock();
+ }
+
+ if (hiddenColumns != null && hiddenColumns.size() > 0)
+ {
+ int vstart = start;
+ int hideStart;
+ int hideEnd;
+
+ for (int[] region : hiddenColumns)
+ {
+ hideStart = region[0];
+ hideEnd = region[1];
+
+ // navigate to start
+ if (hideEnd < vstart)
+ {
+ continue;
+ }
+ if (hideStart > vstart)
+ {
+ int[] contig = new int[] { vstart, hideStart - 1 };
+ vcontigs.add(contig);
+ }
+ vstart = hideEnd + 1;
+
+ // exit if we're past the end
+ if (vstart >= end)
+ {
+ break;
+ }
+ }
+
+ if (vstart < end)
+ {
+ int[] contig = new int[] { vstart, end - 1 };
+ vcontigs.add(contig);
+ }
+ }
+ else
+ {
+ int[] contig = new int[] { start, end - 1 };
+ vcontigs.add(contig);
+ }
+ } finally
+ {
+ if (usecopy)
+ {
+ LOCK.readLock().unlock();
+ }
+ }
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return (currentPosition < vcontigs.size());
+ }
+
+ @Override
+ public int[] next()
+ {
+ int[] result = vcontigs.get(currentPosition);
+ currentPosition++;
+ return result;
+ }
+ }
+
+ /**
+ * An iterator which iterates over visible regions in a range. The range is
+ * specified in terms of visible column positions. Provides a special
+ * "endsAtHidden" indicator to allow callers to determine if the final visible
+ * column is adjacent to a hidden region.
+ */
+ public class VisibleBlocksVisBoundsIterator implements Iterator<int[]>
+ {
+ private List<int[]> vcontigs = new ArrayList<>();
+
+ private int currentPosition = 0;
+
+ private boolean endsAtHidden = false;
+
+ /**
+ * Constructor for iterator over visible regions in a range.
+ *
+ * @param start
+ * start position in terms of visible column position
+ * @param end
+ * end position in terms of visible column position
+ * @param usecopy
+ * whether to use a local copy of hidden columns
+ */
+ VisibleBlocksVisBoundsIterator(int start, int end, boolean usecopy)
+ {
+ /* actually this implementation always uses a local copy but this may change in future */
+ try
+ {
+ if (usecopy)
+ {
+ LOCK.readLock().lock();
+ }
+
+ if (hiddenColumns != null && hiddenColumns.size() > 0)
+ {
+ int blockStart = start;
+ int blockEnd = end;
+ int hiddenSoFar = 0;
+ int visSoFar = 0;
+
+ // iterate until a region begins within (start,end]
+ int i = 0;
+ while ((i < hiddenColumns.size())
+ && (hiddenColumns.get(i)[0] <= blockStart + hiddenSoFar))
+ {
+ hiddenSoFar += hiddenColumns.get(i)[1] - hiddenColumns.get(i)[0]
+ + 1;
+ i++;
+ }
+
+ blockStart += hiddenSoFar; // convert start to absolute position
+ blockEnd += hiddenSoFar; // convert end to absolute position
+
+ // iterate from start to end, adding each visible region. Positions
+ // are
+ // absolute, and all hidden regions which overlap [start,end] are
+ // used.
+ while (i < hiddenColumns.size()
+ && (hiddenColumns.get(i)[0] <= blockEnd))
+ {
+ int[] region = hiddenColumns.get(i);
+
+ // end position of this visible region is either just before the
+ // start of the next hidden region, or the absolute position of
+ // 'end', whichever is lowest
+ blockEnd = Math.min(blockEnd, region[0] - 1);
+
+ vcontigs.add(new int[] { blockStart, blockEnd });
+
+ visSoFar += blockEnd - blockStart + 1;
+
+ // next visible region starts after this hidden region
+ blockStart = region[1] + 1;
+
+ hiddenSoFar += region[1] - region[0] + 1;
+
+ // reset blockEnd to absolute position of 'end', assuming we've now
+ // passed all hidden regions before end
+ blockEnd = end + hiddenSoFar;
+
+ i++;
+ }
+ if (visSoFar < end - start)
+ {
+ // the number of visible columns we've accounted for is less than
+ // the number specified by end-start; work out the end position of
+ // the last visible region
+ blockEnd = blockStart + end - start - visSoFar;
+ vcontigs.add(new int[] { blockStart, blockEnd });
+
+ // if the last visible region ends at the next hidden region, set
+ // endsAtHidden=true
+ if (i < hiddenColumns.size()
+ && hiddenColumns.get(i)[0] - 1 == blockEnd)
+ {
+ endsAtHidden = true;
+ }
+ }
+ }
+ else
+ {
+ // there are no hidden columns, return a single visible contig
+ vcontigs.add(new int[] { start, end });
+ endsAtHidden = false;
+ }
+ } finally
+ {
+ if (usecopy)
+ {
+ LOCK.readLock().unlock();
+ }
+ }
+ }
+
+ @Override
+ public boolean hasNext()
+ {
+ return (currentPosition < vcontigs.size());
+ }
+
+ @Override
+ public int[] next()
+ {
+ int[] result = vcontigs.get(currentPosition);
+ currentPosition++;
+ return result;
+ }
+
+ public boolean endsAtHidden()
+ {
+ return endsAtHidden;
+ }
+ }