X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Fjalview%2Fdatamodel%2FHiddenColumns.java;h=c77fb7b5e6fa56c1bd6eb118063d40dce3cecc40;hb=14aeb3c39e60a604bdeed33949ba05e0c8c8be5d;hp=9722c0a1b12a7bab5685a5e488583f97af91cf74;hpb=5e8ec77d921ff2d604811e5e4ba7e9211b0f48de;p=jalview.git diff --git a/src/jalview/datamodel/HiddenColumns.java b/src/jalview/datamodel/HiddenColumns.java index 9722c0a..c77fb7b 100644 --- a/src/jalview/datamodel/HiddenColumns.java +++ b/src/jalview/datamodel/HiddenColumns.java @@ -1,38 +1,168 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel; import jalview.util.Comparison; -import jalview.util.ShiftList; import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import java.util.Vector; import java.util.concurrent.locks.ReentrantReadWriteLock; -public class HiddenColumns implements Iterable +public class HiddenColumns { - private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - + private static final int HASH_MULTIPLIER = 31; + + private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock(); + /* * list of hidden column [start, end] ranges; the list is maintained in * ascending start column order */ - private Vector hiddenColumns; + private ArrayList hiddenColumns; + + /** + * Constructor + */ + public HiddenColumns() + { + } + + /** + * Copy constructor + * + * @param copy + */ + public HiddenColumns(HiddenColumns copy) + { + try + { + LOCK.writeLock().lock(); + if (copy != null) + { + if (copy.hiddenColumns != null) + { + hiddenColumns = copy.copyHiddenRegionsToArrayList(0); + } + } + } finally + { + LOCK.writeLock().unlock(); + } + } + + /** + * Copy constructor within bounds and with offset. Copies hidden column + * regions fully contained between start and end, and offsets positions by + * subtracting offset. + * + * @param copy + * HiddenColumns instance to copy from + * @param start + * lower bound to copy from + * @param end + * upper bound to copy to + * @param offset + * offset to subtract from each region boundary position + * + */ + public HiddenColumns(HiddenColumns copy, int start, int end, int offset) + { + try + { + LOCK.writeLock().lock(); + if (copy != null) + { + hiddenColumns = new ArrayList<>(); + Iterator it = copy.getBoundedIterator(start, end); + while (it.hasNext()) + { + int[] region = it.next(); + // still need to check boundaries because iterator returns + // all overlapping regions and we need contained regions + if (region[0] >= start && region[1] <= end) + { + hiddenColumns.add( + new int[] + { region[0] - offset, region[1] - offset }); + } + } + } + } finally + { + LOCK.writeLock().unlock(); + } + } /** - * This Method is used to return all the HiddenColumn regions + * This method is used to return all the HiddenColumn regions and is intended + * to remain private. External callers which need a copy of the regions can + * call getHiddenColumnsCopyAsList. * * @return empty list or List of hidden column intervals */ - public List getHiddenRegions() + private List getHiddenRegions() { return hiddenColumns == null ? Collections. emptyList() : hiddenColumns; } /** + * Output regions data as a string. String is in the format: + * reg0[0]reg0[1]reg1[0]reg1[1] ... regn[1] + * + * @param delimiter + * string to delimit regions + * @param betweenstring + * to put between start and end region values + * @return regions formatted according to delimiter and between strings + */ + public String regionsToString(String delimiter, String between) + { + try + { + LOCK.readLock().lock(); + StringBuilder regionBuilder = new StringBuilder(); + if (hiddenColumns != null) + { + for (int[] range : hiddenColumns) + { + regionBuilder.append(delimiter).append(range[0]).append(between) + .append(range[1]); + } + + regionBuilder.deleteCharAt(0); + } + return regionBuilder.toString(); + } finally + { + LOCK.readLock().unlock(); + } + } + + /** * Find the number of hidden columns * * @return number of hidden columns @@ -41,9 +171,9 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); int size = 0; - if (hasHidden()) + if (hasHiddenColumns()) { for (int[] range : hiddenColumns) { @@ -51,29 +181,32 @@ public class HiddenColumns implements Iterable } } return size; - } - finally + } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } /** - * Answers if there are any hidden columns + * Get the number of distinct hidden regions * - * @return true if there are hidden columns + * @return number of regions */ - public boolean hasHidden() + public int getNumberOfRegions() { try { - lock.readLock().lock(); - return (hiddenColumns != null) && (!hiddenColumns.isEmpty()); + LOCK.readLock().lock(); + int num = 0; + if (hasHiddenColumns()) + { + num = hiddenColumns.size(); + } + return num; } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } - } @Override @@ -81,7 +214,7 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); if (!(obj instanceof HiddenColumns)) { @@ -113,7 +246,7 @@ public class HiddenColumns implements Iterable return true; } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -128,13 +261,13 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); int result = column; if (hiddenColumns != null) { for (int i = 0; i < hiddenColumns.size(); i++) { - int[] region = hiddenColumns.elementAt(i); + int[] region = hiddenColumns.get(i); if (result >= region[0]) { result += region[1] - region[0] + 1; @@ -144,7 +277,7 @@ public class HiddenColumns implements Iterable return result; } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -161,7 +294,7 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); int result = hiddenColumn; if (hiddenColumns != null) { @@ -169,7 +302,7 @@ public class HiddenColumns implements Iterable int[] region; do { - region = hiddenColumns.elementAt(index++); + region = hiddenColumns.get(index++); if (hiddenColumn > region[1]) { result -= region[1] + 1 - region[0]; @@ -203,7 +336,7 @@ public class HiddenColumns implements Iterable // columns. } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -223,93 +356,108 @@ public class HiddenColumns implements Iterable try { - lock.readLock().lock(); - int distance = visibleDistance; + LOCK.readLock().lock(); + int distance = visibleDistance; - // in case startColumn is in a hidden region, move it to the left - int start = adjustForHiddenColumns(findColumnPosition(startColumn)); + // in case startColumn is in a hidden region, move it to the left + int start = adjustForHiddenColumns(findColumnPosition(startColumn)); - // get index of hidden region to left of start - int index = getHiddenIndexLeft(start); - if (index == -1) - { - // no hidden regions to left of startColumn - return start - distance; - } + // get index of hidden region to left of start + int index = getHiddenIndexLeft(start); + if (index == -1) + { + // no hidden regions to left of startColumn + return start - distance; + } - // walk backwards through the alignment subtracting the counts of visible - // columns from distance - int[] region; - int gap = 0; - int nextstart = start; + // walk backwards through the alignment subtracting the counts of visible + // columns from distance + int[] region; + int gap = 0; + int nextstart = start; - while ((index > -1) && (distance - gap > 0)) - { - // subtract the gap to right of region from distance - distance -= gap; - start = nextstart; + while ((index > -1) && (distance - gap > 0)) + { + // subtract the gap to right of region from distance + distance -= gap; + start = nextstart; - // calculate the next gap - region = hiddenColumns.get(index); - gap = start - region[1]; + // calculate the next gap + region = hiddenColumns.get(index); + gap = start - region[1]; - // set start to just to left of current region - nextstart = region[0] - 1; - index--; - } + // set start to just to left of current region + nextstart = region[0] - 1; + index--; + } - if (distance - gap > 0) - { - // fell out of loop because there are no more hidden regions - distance -= gap; - return nextstart - distance; - } - return start - distance; + if (distance - gap > 0) + { + // fell out of loop because there are no more hidden regions + distance -= gap; + return nextstart - distance; + } + return start - distance; } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } /** - * Use this method to determine where the next hiddenRegion starts + * Use this method to determine the set of hiddenRegion start positions + * between absolute position and absolute position * - * @param hiddenRegion - * index of hidden region (counts from 0) - * @return column number in visible view + * @param start + * absolute residue to start from + * @param end + * absolute residue to end at + * + * @return list of column numbers in *visible* view where hidden regions start */ - public int findHiddenRegionPosition(int hiddenRegion) + public List findHiddenRegionPositions(int start, int end) { try { - lock.readLock().lock(); - int result = 0; + LOCK.readLock().lock(); + List positions = null; + if (hiddenColumns != null) { - int index = 0; - int gaps = 0; - do - { - int[] region = hiddenColumns.elementAt(index); - if (hiddenRegion == 0) - { - return region[0]; - } + positions = new ArrayList<>(hiddenColumns.size()); - gaps += region[1] + 1 - region[0]; - result = region[1] + 1; - index++; - } while (index <= hiddenRegion); + // navigate to start, keeping count of hidden columns + int i = 0; + int hiddenSoFar = 0; + while ((i < hiddenColumns.size()) + && (hiddenColumns.get(i)[0] < start)) + { + int[] region = hiddenColumns.get(i); + hiddenSoFar += region[1] - region[0] + 1; + i++; + } - result -= gaps; + // 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)) + { + int[] region = hiddenColumns.get(i); + positions.add(region[0] - hiddenSoFar); + hiddenSoFar += region[1] - region[0] + 1; + i++; + } + } + else + { + positions = new ArrayList<>(); } - return result; - } - finally + return positions; + } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -324,13 +472,13 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); if (hiddenColumns != null) { int index = 0; do { - int[] region = hiddenColumns.elementAt(index); + int[] region = hiddenColumns.get(index); if (alPos < region[0]) { return region[0]; @@ -343,7 +491,7 @@ public class HiddenColumns implements Iterable return alPos; } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -359,27 +507,27 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); - if (hiddenColumns != null) - { - int index = hiddenColumns.size() - 1; - do + if (hiddenColumns != null) { - int[] region = hiddenColumns.elementAt(index); - if (alPos > region[1]) + int index = hiddenColumns.size() - 1; + do { - return region[1]; - } + int[] region = hiddenColumns.get(index); + if (alPos > region[1]) + { + return region[1]; + } - index--; - } while (index > -1); - } + index--; + } while (index > -1); + } - return alPos; + return alPos; } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -396,26 +544,26 @@ public class HiddenColumns implements Iterable try { - lock.readLock().lock(); - if (hiddenColumns != null) - { - int index = hiddenColumns.size() - 1; - do + LOCK.readLock().lock(); + if (hiddenColumns != null) { - int[] region = hiddenColumns.elementAt(index); - if (pos > region[1]) + int index = hiddenColumns.size() - 1; + do { - return index; - } + int[] region = hiddenColumns.get(index); + if (pos > region[1]) + { + return index; + } - index--; - } while (index > -1); - } + index--; + } while (index > -1); + } - return -1; + return -1; } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -428,96 +576,91 @@ public class HiddenColumns implements Iterable */ public void hideColumns(int start, int end) { - hideColumns(start, end, false); - } - - /** - * Adds the specified column range to the hidden columns - * - * @param start - * @param end - */ - private void hideColumns(int start, int end, boolean alreadyLocked) - { + boolean wasAlreadyLocked = false; try { - - if (!alreadyLocked) + // check if the write lock was already locked by this thread, + // as this method can be called internally in loops within HiddenColumns + if (!LOCK.isWriteLockedByCurrentThread()) { - lock.writeLock().lock(); + LOCK.writeLock().lock(); } - - if (hiddenColumns == null) - { - hiddenColumns = new Vector<>(); - } - - /* - * traverse existing hidden ranges and insert / amend / append as - * appropriate - */ - for (int i = 0; i < hiddenColumns.size(); i++) - { - int[] region = hiddenColumns.elementAt(i); - - if (end < region[0] - 1) + else { - /* - * insert discontiguous preceding range - */ - hiddenColumns.insertElementAt(new int[] { start, end }, i); - return; + wasAlreadyLocked = true; } - if (end <= region[1]) + if (hiddenColumns == null) { - /* - * new range overlaps existing, or is contiguous preceding it - adjust - * start column - */ - region[0] = Math.min(region[0], start); - return; + hiddenColumns = new ArrayList<>(); } - if (start <= region[1] + 1) + /* + * traverse existing hidden ranges and insert / amend / append as + * appropriate + */ + for (int i = 0; i < hiddenColumns.size(); i++) { - /* - * new range overlaps existing, or is contiguous following it - adjust - * start and end columns - */ - region[0] = Math.min(region[0], start); - region[1] = Math.max(region[1], end); + int[] region = hiddenColumns.get(i); + + if (end < region[0] - 1) + { + /* + * insert discontiguous preceding range + */ + hiddenColumns.add(i, new int[] { start, end }); + return; + } - /* - * also update or remove any subsequent ranges - * that are overlapped - */ - while (i < hiddenColumns.size() - 1) + if (end <= region[1]) { - int[] nextRegion = hiddenColumns.get(i + 1); - if (nextRegion[0] > end + 1) + /* + * new range overlaps existing, or is contiguous preceding it - adjust + * start column + */ + region[0] = Math.min(region[0], start); + return; + } + + if (start <= region[1] + 1) + { + /* + * new range overlaps existing, or is contiguous following it - adjust + * start and end columns + */ + region[0] = Math.min(region[0], start); + region[1] = Math.max(region[1], end); + + /* + * also update or remove any subsequent ranges + * that are overlapped + */ + while (i < hiddenColumns.size() - 1) { - /* - * gap to next hidden range - no more to update - */ - break; + int[] nextRegion = hiddenColumns.get(i + 1); + if (nextRegion[0] > end + 1) + { + /* + * gap to next hidden range - no more to update + */ + break; + } + region[1] = Math.max(nextRegion[1], end); + hiddenColumns.remove(i + 1); } - region[1] = Math.max(nextRegion[1], end); - hiddenColumns.remove(i + 1); + return; } - return; } - } - /* - * remaining case is that the new range follows everything else - */ - hiddenColumns.addElement(new int[] { start, end }); + /* + * remaining case is that the new range follows everything else + */ + hiddenColumns.add(new int[] { start, end }); } finally { - if (!alreadyLocked) + if (!wasAlreadyLocked) { - lock.writeLock().unlock(); + LOCK.writeLock().unlock(); } } } @@ -526,82 +669,40 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); - if (hiddenColumns != null) - { - for (int[] region : hiddenColumns) + if (hiddenColumns != null) { - if (column >= region[0] && column <= region[1]) + for (int[] region : hiddenColumns) { - return false; + if (column >= region[0] && column <= region[1]) + { + return false; + } } } - } - return true; + return true; } finally { - lock.readLock().unlock(); - } - } - - /** - * ColumnSelection - */ - public HiddenColumns() - { - } - - /** - * Copy constructor - * - * @param copy - */ - public HiddenColumns(HiddenColumns copy) - { - try - { - - lock.readLock().lock(); - if (copy != null) - { - if (copy.hiddenColumns != null) - { - hiddenColumns = copy.copyHiddenRegions(); - } - } - } - finally - { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } - private Vector copyHiddenRegions() + private ArrayList copyHiddenRegionsToArrayList(int startIndex) { - Vector copy = new Vector<>(hiddenColumns.size()); - for (int i = 0, j = hiddenColumns.size(); i < j; i++) + int size = 0; + if (hiddenColumns != null) { - int[] rh, cp; - rh = hiddenColumns.elementAt(i); - if (rh != null) - { - cp = new int[rh.length]; - System.arraycopy(rh, 0, cp, 0, rh.length); - copy.addElement(cp); - } + size = hiddenColumns.size(); } - return copy; - } + ArrayList copy = new ArrayList<>(size); - private ArrayList copyHiddenRegionsToArrayList() - { - ArrayList copy = new ArrayList<>(hiddenColumns.size()); - for (int i = 0, j = hiddenColumns.size(); i < j; i++) + for (int i = startIndex, j = size; i < j; i++) { - int[] rh, cp; - rh = hiddenColumns.elementAt(i); + int[] rh; + int[] cp; + rh = hiddenColumns.get(i); if (rh != null) { cp = new int[rh.length]; @@ -609,202 +710,71 @@ public class HiddenColumns implements Iterable copy.add(cp); } } - return copy; - } - - public void getHiddenColumnsCopy(Vector copy) - { - try - { - lock.readLock().lock(); - copy = copyHiddenRegions(); - } finally - { - lock.readLock().unlock(); - } - } - public void getHiddenColumnsCopy(ArrayList copy) - { - try - { - lock.readLock().lock(); - copy = copyHiddenRegionsToArrayList(); - } finally - { - lock.readLock().unlock(); - } + return copy; } /** - * propagate shift in alignment columns to column selection + * return all visible segments between the given start and end boundaries * * @param start - * beginning of edit - * @param left - * shift in edit (+ve for removal, or -ve for inserts) + * (first column inclusive from 0) + * @param end + * (last column - not inclusive) + * @return List {[i_start, i_end], ..} where intervals lie in + * start<=i_start<=i_end compensateForEdit(int start, int change, - ColumnSelection sel) + public List getVisibleContigs(int start, int end) { try { - lock.writeLock().lock(); - List deletedHiddenColumns = null; - - if (hiddenColumns != null) + LOCK.readLock().lock(); + List vcontigs = new ArrayList<>(); + if (hiddenColumns != null && hiddenColumns.size() > 0) { - deletedHiddenColumns = new ArrayList<>(); - int hSize = hiddenColumns.size(); - for (int i = 0; i < hSize; i++) + int vstart = start; + int hideStart; + int hideEnd; + + for (int[] region : hiddenColumns) { - int[] region = hiddenColumns.elementAt(i); - if (region[0] > start && start + change > region[1]) - { - deletedHiddenColumns.add(region); + hideStart = region[0]; + hideEnd = region[1]; - hiddenColumns.removeElementAt(i); - i--; - hSize--; + // navigate to start + if (hideEnd < vstart) + { continue; } - - if (region[0] > start) + if (hideStart > vstart) { - region[0] -= change; - region[1] -= change; + int[] contig = new int[] { vstart, hideStart - 1 }; + vcontigs.add(contig); } + vstart = hideEnd + 1; - if (region[0] < 0) + // exit if we're past the end + if (vstart >= end) { - region[0] = 0; + break; } - - } - - this.revealHiddenColumns(0, sel); - } - - return deletedHiddenColumns; - } finally - { - lock.writeLock().unlock(); - } - } - - /** - * propagate shift in alignment columns to column selection special version of - * compensateForEdit - allowing for edits within hidden regions - * - * @param start - * beginning of edit - * @param left - * shift in edit (+ve for removal, or -ve for inserts) - */ - public void compensateForDelEdits(int start, int change) - { - try - { - lock.writeLock().lock(); - if (hiddenColumns != null) - { - for (int i = 0; i < hiddenColumns.size(); i++) - { - int[] region = hiddenColumns.elementAt(i); - if (region[0] >= start) - { - region[0] -= change; - } - if (region[1] >= start) - { - region[1] -= change; - } - if (region[1] < region[0]) - { - hiddenColumns.removeElementAt(i--); - } - - if (region[0] < 0) - { - region[0] = 0; - } - if (region[1] < 0) - { - region[1] = 0; - } - } - } - } - finally - { - lock.writeLock().unlock(); - } - } - - /** - * return all visible segments between the given start and end boundaries - * - * @param start - * (first column inclusive from 0) - * @param end - * (last column - not inclusive) - * @return int[] {i_start, i_end, ..} where intervals lie in - * start<=i_start<=i_end 0) - { - List visiblecontigs = new ArrayList<>(); - List regions = getHiddenRegions(); - - int vstart = start; - int[] region; - int hideStart, hideEnd; - - for (int j = 0; vstart < end && j < regions.size(); j++) - { - region = regions.get(j); - hideStart = region[0]; - hideEnd = region[1]; - - if (hideEnd < vstart) - { - continue; - } - if (hideStart > vstart) - { - visiblecontigs.add(new int[] { vstart, hideStart - 1 }); - } - vstart = hideEnd + 1; } if (vstart < end) { - visiblecontigs.add(new int[] { vstart, end - 1 }); + int[] contig = new int[] { vstart, end - 1 }; + vcontigs.add(contig); } - int[] vcontigs = new int[visiblecontigs.size() * 2]; - for (int i = 0, j = visiblecontigs.size(); i < j; i++) - { - int[] vc = visiblecontigs.get(i); - visiblecontigs.set(i, null); - vcontigs[i * 2] = vc[0]; - vcontigs[i * 2 + 1] = vc[1]; - } - visiblecontigs.clear(); - return vcontigs; } else { - return new int[] { start, end - 1 }; + int[] contig = new int[] { start, end - 1 }; + vcontigs.add(contig); } - } - finally + return vcontigs; + } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -813,23 +783,22 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); - int i, iSize = seqs.length; - String selections[] = new String[iSize]; + LOCK.readLock().lock(); + int iSize = seqs.length; + String[] selections = new String[iSize]; if (hiddenColumns != null && hiddenColumns.size() > 0) { - for (i = 0; i < iSize; i++) + for (int i = 0; i < iSize; i++) { StringBuffer visibleSeq = new StringBuffer(); - List regions = getHiddenRegions(); - int blockStart = start, blockEnd = end; - int[] region; - int hideStart, hideEnd; + int blockStart = start; + int blockEnd = end; + int hideStart; + int hideEnd; - for (int j = 0; j < regions.size(); j++) + for (int[] region : hiddenColumns) { - region = regions.get(j); hideStart = region[0]; hideEnd = region[1]; @@ -862,17 +831,16 @@ public class HiddenColumns implements Iterable } else { - for (i = 0; i < iSize; i++) + for (int i = 0; i < iSize; i++) { selections[i] = seqs[i].getSequenceAsString(start, end); } } return selections; - } - finally + } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -890,23 +858,29 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); - int fpos = seq.getStart(), lpos = seq.getEnd(); + LOCK.readLock().lock(); + int fpos = seq.getStart(); + int lpos = seq.getEnd(); int start = 0; if (hiddenColumns == null || hiddenColumns.size() == 0) { - int ifpos = seq.findIndex(fpos) - 1, - ilpos = seq.findIndex(lpos) - 1; - return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos }; + int ifpos = seq.findIndex(fpos) - 1; + int ilpos = seq.findIndex(lpos) - 1; + return new int[] { ifpos, ifpos, ilpos }; } // Simply walk along the sequence whilst watching for hidden column // boundaries List regions = getHiddenRegions(); - int spos = fpos, lastvispos = -1, rcount = 0, - hideStart = seq.getLength(), hideEnd = -1; - int visPrev = 0, visNext = 0, firstP = -1, lastP = -1; + int spos = fpos; + int rcount = 0; + int hideStart = seq.getLength(); + int hideEnd = -1; + int visPrev = 0; + int visNext = 0; + int firstP = -1; + int lastP = -1; boolean foundStart = false; for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd() && p < pLen; p++) @@ -942,7 +916,6 @@ public class HiddenColumns implements Iterable start = p; foundStart = true; } - lastvispos = p; lpos = spos; } // look for next sequence position @@ -951,15 +924,13 @@ public class HiddenColumns implements Iterable } if (foundStart) { - return new int[] { findColumnPosition(start), - findColumnPosition(lastvispos), fpos, lpos, firstP, lastP }; + return new int[] { findColumnPosition(start), firstP, lastP }; } // otherwise, sequence was completely hidden - return new int[] { visPrev, visNext, 0, 0, firstP, lastP }; - } - finally + return new int[] { visPrev, firstP, lastP }; + } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -990,7 +961,7 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); if (alignmentAnnotation.annotations == null) { return; @@ -1005,14 +976,14 @@ public class HiddenColumns implements Iterable // then mangle the alignmentAnnotation annotation array Vector annels = new Vector<>(); Annotation[] els = null; - List regions = getHiddenRegions(); - int blockStart = start, blockEnd = end; - int[] region; - int hideStart, hideEnd, w = 0; + int blockStart = start; + int blockEnd = end; + int hideStart; + int hideEnd; + int w = 0; - for (int j = 0; j < regions.size(); j++) + for (int[] region : hiddenColumns) { - region = regions.get(j); hideStart = region[0]; hideEnd = region[1]; @@ -1029,7 +1000,8 @@ public class HiddenColumns implements Iterable break; } - annels.addElement(els = new Annotation[blockEnd - blockStart]); + els = new Annotation[blockEnd - blockStart]; + annels.addElement(els); System.arraycopy(alignmentAnnotation.annotations, blockStart, els, 0, els.length); w += els.length; @@ -1039,7 +1011,8 @@ public class HiddenColumns implements Iterable if (end > blockStart) { - annels.addElement(els = new Annotation[end - blockStart + 1]); + els = new Annotation[end - blockStart + 1]; + annels.addElement(els); if ((els.length + blockStart) <= alignmentAnnotation.annotations.length) { @@ -1075,10 +1048,9 @@ public class HiddenColumns implements Iterable { alignmentAnnotation.restrict(start, end); } - } - finally + } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -1090,11 +1062,11 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); return hiddenColumns != null && hiddenColumns.size() > 0; } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -1106,11 +1078,11 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); return hiddenColumns != null && hiddenColumns.size() > 1; } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -1124,15 +1096,15 @@ public class HiddenColumns implements Iterable { try { - lock.writeLock().lock(); + LOCK.writeLock().lock(); List inserts = sr.getInsertions(); for (int[] r : inserts) { - hideColumns(r[0], r[1], true); + hideColumns(r[0], r[1]); } } finally { - lock.writeLock().unlock(); + LOCK.writeLock().unlock(); } } @@ -1143,12 +1115,12 @@ public class HiddenColumns implements Iterable { try { - lock.writeLock().lock(); + LOCK.writeLock().lock(); if (hiddenColumns != null) { for (int i = 0; i < hiddenColumns.size(); i++) { - int[] region = hiddenColumns.elementAt(i); + int[] region = hiddenColumns.get(i); for (int j = region[0]; j < region[1] + 1; j++) { sel.addElement(j); @@ -1157,10 +1129,9 @@ public class HiddenColumns implements Iterable } hiddenColumns = null; - } - finally + } finally { - lock.writeLock().unlock(); + LOCK.writeLock().unlock(); } } @@ -1174,10 +1145,10 @@ public class HiddenColumns implements Iterable { try { - lock.writeLock().lock(); + LOCK.writeLock().lock(); for (int i = 0; i < hiddenColumns.size(); i++) { - int[] region = hiddenColumns.elementAt(i); + int[] region = hiddenColumns.get(i); if (start == region[0]) { for (int j = region[0]; j < region[1] + 1; j++) @@ -1185,7 +1156,7 @@ public class HiddenColumns implements Iterable sel.addElement(j); } - hiddenColumns.removeElement(region); + hiddenColumns.remove(region); break; } } @@ -1193,132 +1164,9 @@ public class HiddenColumns implements Iterable { hiddenColumns = null; } - } - finally - { - lock.writeLock().unlock(); - } - } - - /** - * removes intersection of position,length ranges in deletions from the - * start,end regions marked in intervals. - * - * @param shifts - * @param intervals - * @return - */ - private boolean pruneIntervalVector(final List shifts, - Vector intervals) - { - boolean pruned = false; - int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1; - int hr[] = intervals.elementAt(i); - int sr[] = shifts.get(s); - while (i <= j && s <= t) - { - boolean trailinghn = hr[1] >= sr[0]; - if (!trailinghn) - { - if (i < j) - { - hr = intervals.elementAt(++i); - } - else - { - i++; - } - continue; - } - int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert - if (endshift < hr[0] || endshift < sr[0]) - { // leadinghc disjoint or not a deletion - if (s < t) - { - sr = shifts.get(++s); - } - else - { - s++; - } - continue; - } - boolean leadinghn = hr[0] >= sr[0]; - boolean leadinghc = hr[0] < endshift; - boolean trailinghc = hr[1] < endshift; - if (leadinghn) - { - if (trailinghc) - { // deleted hidden region. - intervals.removeElementAt(i); - pruned = true; - j--; - if (i <= j) - { - hr = intervals.elementAt(i); - } - continue; - } - if (leadinghc) - { - hr[0] = endshift; // clip c terminal region - leadinghn = !leadinghn; - pruned = true; - } - } - if (!leadinghn) - { - if (trailinghc) - { - if (trailinghn) - { - hr[1] = sr[0] - 1; - pruned = true; - } - } - else - { - // sr contained in hr - if (s < t) - { - sr = shifts.get(++s); - } - else - { - s++; - } - continue; - } - } - } - return pruned; // true if any interval was removed or modified by - // operations. - } - - /** - * remove any hiddenColumns or selected columns and shift remaining based on a - * series of position, range deletions. - * - * @param deletions - */ - public void pruneDeletions(List shifts) - { - try - { - lock.writeLock().lock(); - // delete any intervals intersecting. - if (hiddenColumns != null) - { - pruneIntervalVector(shifts, hiddenColumns); - if (hiddenColumns != null && hiddenColumns.size() == 0) - { - hiddenColumns = null; - } - } - } - finally + } finally { - lock.writeLock().unlock(); + LOCK.writeLock().unlock(); } } @@ -1362,154 +1210,114 @@ public class HiddenColumns implements Iterable SequenceI origseq) { char gc = al.getGapCharacter(); - // recover mapping between sequence's non-gap positions and positions - // mapping to view. - pruneDeletions(ShiftList.parseMap(origseq.gapMap())); - int[] viscontigs = al.getHiddenColumns().getVisibleContigs(0, - profileseq.getLength()); - int spos = 0; - int offset = 0; - // add profile to visible contigs - for (int v = 0; v < viscontigs.length; v += 2) - { - if (viscontigs[v] > spos) + // take the set of hidden columns, and the set of gaps in origseq, + // and remove all the hidden gaps from hiddenColumns + + // first get the gaps as a Bitset + BitSet gaps = origseq.gapBitset(); + + // now calculate hidden ^ not(gap) + BitSet hidden = new BitSet(); + markHiddenRegions(hidden); + hidden.andNot(gaps); + hiddenColumns = null; + this.hideMarkedBits(hidden); + + // for each sequence in the alignment, except the profile sequence, + // insert gaps corresponding to each hidden region + // but where each hidden column region is shifted backwards by the number of + // preceding visible gaps + // update hidden columns at the same time + Iterator regions = iterator(); + ArrayList newhidden = new ArrayList<>(); + + int numGapsBefore = 0; + int gapPosition = 0; + while (regions.hasNext()) + { + // get region coordinates accounting for gaps + // we can rely on gaps not being *in* hidden regions because we already + // removed those + int[] region = regions.next(); + while (gapPosition < region[0]) { - StringBuffer sb = new StringBuffer(); - for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++) + gapPosition++; + if (gaps.get(gapPosition)) { - sb.append(gc); + numGapsBefore++; } - for (int s = 0, ns = al.getHeight(); s < ns; s++) - { - SequenceI sqobj = al.getSequenceAt(s); - if (sqobj != profileseq) - { - String sq = al.getSequenceAt(s).getSequenceAsString(); - if (sq.length() <= spos + offset) - { - // pad sequence - int diff = spos + offset - sq.length() - 1; - if (diff > 0) - { - // pad gaps - sq = sq + sb; - while ((diff = spos + offset - sq.length() - 1) > 0) - { - // sq = sq - // + ((diff >= sb.length()) ? sb.toString() : sb - // .substring(0, diff)); - if (diff >= sb.length()) - { - sq += sb.toString(); - } - else - { - char[] buf = new char[diff]; - sb.getChars(0, diff, buf, 0); - sq += buf.toString(); - } - } - } - sq += sb.toString(); - } - else - { - al.getSequenceAt(s).setSequence( - sq.substring(0, spos + offset) + sb.toString() - + sq.substring(spos + offset)); - } - } - } - // offset+=sb.length(); } - spos = viscontigs[v + 1] + 1; - } - if ((offset + spos) < profileseq.getLength()) - { - // pad the final region with gaps. + + int left = region[0] - numGapsBefore; + int right = region[1] - numGapsBefore; + newhidden.add(new int[] { left, right }); + + // make a string with number of gaps = length of hidden region StringBuffer sb = new StringBuffer(); - for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++) + for (int s = 0; s < right - left + 1; s++) { sb.append(gc); } - for (int s = 0, ns = al.getHeight(); s < ns; s++) - { - SequenceI sqobj = al.getSequenceAt(s); - if (sqobj == profileseq) - { - continue; - } - String sq = sqobj.getSequenceAsString(); - // pad sequence - int diff = origseq.getLength() - sq.length(); - while (diff > 0) - { - // sq = sq - // + ((diff >= sb.length()) ? sb.toString() : sb - // .substring(0, diff)); - if (diff >= sb.length()) - { - sq += sb.toString(); - } - else - { - char[] buf = new char[diff]; - sb.getChars(0, diff, buf, 0); - sq += buf.toString(); - } - diff = origseq.getLength() - sq.length(); - } - } - } - } + padGaps(sb, left, profileseq, al); - /** - * remove any hiddenColumns or selected columns and shift remaining based on a - * series of position, range deletions. - * - * @param deletions - */ - private void pruneDeletions(ShiftList deletions) - { - if (deletions != null) - { - final List shifts = deletions.getShifts(); - if (shifts != null && shifts.size() > 0) - { - pruneDeletions(shifts); - - // and shift the rest. - this.compensateForEdits(deletions); - } } + hiddenColumns = newhidden; } /** - * Adjust hidden column boundaries based on a series of column additions or - * deletions in visible regions. + * Pad gaps in all sequences in alignment except profileseq * - * @param shiftrecord - * @return + * @param sb + * gap string to insert + * @param left + * position to insert at + * @param profileseq + * sequence not to pad + * @param al + * alignment to pad sequences in */ - private ShiftList compensateForEdits(ShiftList shiftrecord) + private void padGaps(StringBuffer sb, int pos, SequenceI profileseq, + AlignmentI al) { - if (shiftrecord != null) + // loop over the sequences and pad with gaps where required + for (int s = 0, ns = al.getHeight(); s < ns; s++) { - final List shifts = shiftrecord.getShifts(); - if (shifts != null && shifts.size() > 0) + SequenceI sqobj = al.getSequenceAt(s); + if (sqobj != profileseq) { - int shifted = 0; - for (int i = 0, j = shifts.size(); i < j; i++) + String sq = al.getSequenceAt(s).getSequenceAsString(); + if (sq.length() <= pos) + { + // pad sequence + int diff = pos - sq.length() - 1; + if (diff > 0) + { + // pad gaps + sq = sq + sb; + while ((diff = pos - sq.length() - 1) > 0) + { + if (diff >= sb.length()) + { + sq += sb.toString(); + } + else + { + char[] buf = new char[diff]; + sb.getChars(0, diff, buf, 0); + sq += buf.toString(); + } + } + } + sq += sb.toString(); + } + else { - int[] sh = shifts.get(i); - compensateForDelEdits(shifted + sh[0], sh[1]); - shifted -= sh[1]; + al.getSequenceAt(s).setSequence( + sq.substring(0, pos) + sb.toString() + sq.substring(pos)); } } - return shiftrecord.getInverse(); } - return null; } /** @@ -1520,21 +1328,20 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); int hashCode = 1; if (hiddenColumns != null) { for (int[] hidden : hiddenColumns) { - hashCode = 31 * hashCode + hidden[0]; - hashCode = 31 * hashCode + hidden[1]; + hashCode = HASH_MULTIPLIER * hashCode + hidden[0]; + hashCode = HASH_MULTIPLIER * hashCode + hidden[1]; } } return hashCode; - } - finally + } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); } } @@ -1548,17 +1355,17 @@ public class HiddenColumns implements Iterable { try { - lock.writeLock().lock(); + LOCK.writeLock().lock(); for (int firstSet = inserts .nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts .nextSetBit(lastSet)) { lastSet = inserts.nextClearBit(firstSet); - hideColumns(firstSet, lastSet - 1, true); + hideColumns(firstSet, lastSet - 1); } } finally { - lock.writeLock().unlock(); + LOCK.writeLock().unlock(); } } @@ -1571,7 +1378,7 @@ public class HiddenColumns implements Iterable { try { - lock.readLock().lock(); + LOCK.readLock().lock(); if (hiddenColumns == null) { return; @@ -1580,21 +1387,455 @@ public class HiddenColumns implements Iterable { inserts.set(range[0], range[1] + 1); } + } finally + { + LOCK.readLock().unlock(); } - finally + } + + /** + * Calculate the visible start and end index of an alignment. + * + * @param width + * full alignment width + * @return integer array where: int[0] = startIndex, and int[1] = endIndex + */ + public int[] getVisibleStartAndEndIndex(int width) + { + try + { + LOCK.readLock().lock(); + int[] alignmentStartEnd = new int[] { 0, width - 1 }; + int startPos = alignmentStartEnd[0]; + int endPos = alignmentStartEnd[1]; + + int[] lowestRange = new int[] { -1, -1 }; + int[] higestRange = new int[] { -1, -1 }; + + if (hiddenColumns == null) + { + return new int[] { startPos, endPos }; + } + + for (int[] hiddenCol : hiddenColumns) + { + lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange; + higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange; + } + + if (lowestRange[0] == -1 && lowestRange[1] == -1) + { + startPos = alignmentStartEnd[0]; + } + else + { + startPos = lowestRange[1] + 1; + } + + if (higestRange[0] == -1 && higestRange[1] == -1) + { + endPos = alignmentStartEnd[1]; + } + else + { + endPos = higestRange[0] - 1; + } + return new int[] { startPos, endPos }; + } finally { - lock.readLock().unlock(); + LOCK.readLock().unlock(); + } + + } + + /** + * Finds the hidden region (if any) which starts or ends at res + * + * @param res + * visible residue position, unadjusted for hidden columns + * @return region as [start,end] or null if no matching region is found + */ + public int[] getRegionWithEdgeAtRes(int res) + { + try + { + LOCK.readLock().lock(); + int adjres = adjustForHiddenColumns(res); + + int[] reveal = null; + if (hiddenColumns != null) + { + for (int[] region : hiddenColumns) + { + if (adjres + 1 == region[0] || adjres - 1 == region[1]) + { + reveal = region; + break; + } + } + } + return reveal; + } finally + { + LOCK.readLock().unlock(); } } - @Override public Iterator iterator() { - if (hiddenColumns == null) + if (hiddenColumns != null) { - return Collections. emptyList().iterator(); + int last = hiddenColumns.get(hiddenColumns.size() - 1)[1]; + return new BoundedHiddenColsIterator(0, last, true); + } + else + { + return new BoundedHiddenColsIterator(0, 0, true); } - return hiddenColumns.iterator(); } + public Iterator getBoundedIterator(int start, int end) + { + return new BoundedHiddenColsIterator(start, end, true); + } + + public Iterator getBoundedStartIterator(int start, int end) + { + return new BoundedStartRegionIterator(start, end, true); + } + + public Iterator getVisibleColsIterator(int start, int end) + { + return new VisibleColsIterator(start, end, true); + } + + /** + * An iterator which iterates over hidden column regions in a range. + * + * @author kmourao + * + */ + class BoundedHiddenColsIterator implements Iterator + { + private int start; // start position to iterate from + + private int end; // end position to iterate to + + // current index in hiddenColumns + private int currentPosition = 0; + + // current column in hiddenColumns + private int[] currentRegion; + + // whether to make a local copy of hiddenColumns + private final boolean useCopy; + + // local copy or reference to hiddenColumns + private List localHidden; + + /** + * 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) + */ + BoundedHiddenColsIterator(int lowerBound, int upperBound, + boolean useCopyCols) + { + start = lowerBound; + end = upperBound; + useCopy = useCopyCols; + + try + { + if (useCopy) + { + // assume that if useCopy is false the calling code has locked + // hiddenColumns + 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; + int[] cp; + rh = hiddenColumns.get(i); + if (rh != null) + { + cp = new int[rh.length]; + System.arraycopy(rh, 0, cp, 0, rh.length); + localHidden.add(cp); + } + i++; + } + } + } + finally + { + if (useCopy) + { + LOCK.readLock().unlock(); + } + } + } + + @Override + public boolean hasNext() + { + return (localHidden != null) + && (currentPosition < localHidden.size()); + } + + @Override + public int[] next() + { + currentRegion = localHidden.get(currentPosition); + currentPosition++; + return currentRegion; + } + } + + class BoundedStartRegionIterator implements Iterator + { + + private int start; // start position to iterate from + + private int end; // end position to iterate to + + // current index in hiddenColumns + private int currentPosition = 0; + + // local copy or reference to hiddenColumns + private List 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()); + } + + @Override + public Integer next() + { + int result = positions.get(currentPosition); + currentPosition++; + return result; + } + } + + public class VisibleColsIterator implements Iterator + { + private int last; + + private int current; + + private int next; + + private List localHidden = new ArrayList<>(); + + private int lasthiddenregion; + + public VisibleColsIterator(int firstcol, int lastcol, boolean useCopy) + { + last = lastcol; + current = firstcol; + next = firstcol; + lasthiddenregion = -1; + + try + { + if (useCopy) + { + // assume that if useCopy is false the calling code has locked + // hiddenColumns + LOCK.readLock().lock(); + } + + if (hiddenColumns != null) + { + int i = 0; + for (i = 0; i < hiddenColumns.size(); ++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; + } + if (current < hiddenColumns.get(i)[0]) + { + break; + } + } + + lasthiddenregion = i - 1; + + for (i = hiddenColumns.size() - 1; i >= 0; --i) + { + if (last >= hiddenColumns.get(i)[0] + && last <= hiddenColumns.get(i)[1]) + { + // last is hidden, move to left + last = hiddenColumns.get(i)[0] - 1; + } + if (last > hiddenColumns.get(i)[1]) + { + break; + } + } + + // make a local copy of the bit we need + i = lasthiddenregion + 1; + 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++; + } + lasthiddenregion = -1; + } + } finally + { + if (useCopy) + { + 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) + && (lasthiddenregion + 1 < localHidden.size())) + { + // still some more hidden regions + if (next + 1 < localHidden.get(lasthiddenregion + 1)[0]) + { + // next+1 is still before the next hidden region + next++; + } + else if ((next + 1 >= localHidden.get(lasthiddenregion + 1)[0]) + && (next + 1 <= localHidden.get(lasthiddenregion + 1)[1])) + { + // next + 1 is in the next hidden region + next = localHidden.get(lasthiddenregion + 1)[1] + 1; + lasthiddenregion++; + } + } + else + { + // finished with hidden regions, just increment normally + next++; + } + return current; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + } }