X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fdatamodel%2FHiddenColumns.java;h=4a1a9a311d633413371e2820dfa6e417086a209a;hb=b95ecdde59db976d721fe9aec626105947825b9e;hp=1ac6015c2706078f15300dd403cd79f3027eec38;hpb=f57d1d3437767e14c2bf711af0e06c13b9d18cd4;p=jalview.git diff --git a/src/jalview/datamodel/HiddenColumns.java b/src/jalview/datamodel/HiddenColumns.java index 1ac6015..4a1a9a3 100644 --- a/src/jalview/datamodel/HiddenColumns.java +++ b/src/jalview/datamodel/HiddenColumns.java @@ -26,19 +26,50 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; +/** + * This class manages the collection of hidden columns associated with an + * alignment. To iterate over the collection, or over visible columns/regions, + * use an iterator obtained from one of: + * + * - getBoundedIterator: iterates over the hidden regions, within some bounds, + * returning absolute positions + * + * - getBoundedStartIterator: iterates over the start positions of hidden + * regions, within some bounds, returning visible positions + * + * - getVisContigsIterator: iterates over visible regions in a range, returning + * absolute positions + * + * - getVisibleColsIterator: iterates over the visible *columns* + * + * For performance reasons, provide bounds where possible. Note that column + * numbering begins at 0 throughout this class. + * + * @author kmourao + * + */ public class HiddenColumns { private static final int HASH_MULTIPLIER = 31; private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock(); + /* + * Cursor which tracks the last used hidden columns region, and the number + * of hidden columns up to (but not including) that region. + */ private HiddenColumnsCursor cursor = new HiddenColumnsCursor(); /* + * cache of the number of hidden columns + */ + private int numColumns = 0; + + /* * list of hidden column [start, end] ranges; the list is maintained in * ascending start column order */ - private ArrayList hiddenColumns; + private List hiddenColumns; /** * Constructor @@ -47,33 +78,23 @@ public class HiddenColumns { } + /* + * Methods which change the hiddenColumns collection. These methods should + * use a writeLock to prevent other threads accessing the hiddenColumns + * collection while changes are being made. They should also reset the hidden + * columns cursor, and either update the hidden columns count, or set it to 0 + * (so that it will later be updated when needed). + */ + /** * Copy constructor * * @param copy + * the HiddenColumns object to copy from */ public HiddenColumns(HiddenColumns copy) { - try - { - LOCK.writeLock().lock(); - if (copy != null) - { - if (copy.hiddenColumns != null) - { - hiddenColumns = new ArrayList<>(); - Iterator it = copy.iterator(); - while (it.hasNext()) - { - hiddenColumns.add(it.next()); - } - cursor.resetCursor(hiddenColumns); - } - } - } finally - { - LOCK.writeLock().unlock(); - } + this(copy, Integer.MIN_VALUE, Integer.MAX_VALUE, 0); } /** @@ -99,6 +120,7 @@ public class HiddenColumns if (copy != null) { hiddenColumns = new ArrayList<>(); + numColumns = 0; Iterator it = copy.getBoundedIterator(start, end); while (it.hasNext()) { @@ -110,6 +132,7 @@ public class HiddenColumns hiddenColumns.add( new int[] { region[0] - offset, region[1] - offset }); + numColumns += region[1] - region[0] + 1; } } cursor.resetCursor(hiddenColumns); @@ -121,6 +144,432 @@ public class HiddenColumns } /** + * Adds the specified column range to the hidden columns collection + * + * @param start + * start of range to add (absolute position in alignment) + * @param end + * end of range to add (absolute position in alignment) + */ + public void hideColumns(int start, int end) + { + boolean wasAlreadyLocked = false; + try + { + // 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(); + } + else + { + wasAlreadyLocked = true; + } + + int previndex = 0; + int prevHiddenCount = 0; + int regionindex = 0; + if (hiddenColumns == null) + { + hiddenColumns = new ArrayList<>(); + } + else + { + // set up cursor reset values + HiddenCursorPosition cursorPos = cursor.findRegionForColumn(start); + regionindex = cursorPos.getRegionIndex(); + + if (regionindex > 0) + { + // get previous index and hidden count for updating the cursor later + previndex = regionindex - 1; + int[] prevRegion = hiddenColumns.get(previndex); + prevHiddenCount = cursorPos.getHiddenSoFar() + - (prevRegion[1] - prevRegion[0] + 1); + } + } + + /* + * new range follows everything else; check first to avoid looping over whole hiddenColumns collection + */ + if (hiddenColumns.isEmpty() + || start > hiddenColumns.get(hiddenColumns.size() - 1)[1]) + { + hiddenColumns.add(new int[] { start, end }); + } + else + { + /* + * traverse existing hidden ranges and insert / amend / append as + * appropriate + */ + boolean added = false; + if (regionindex > 0) + { + added = insertRangeAtRegion(regionindex - 1, start, end); + } + if (!added && regionindex < hiddenColumns.size()) + { + insertRangeAtRegion(regionindex, start, end); + } + } + + // reset the cursor to just before our insertion point: this saves + // a lot of reprocessing in large alignments + cursor.resetCursor(hiddenColumns, previndex, prevHiddenCount); + + // reset the number of columns so they will be recounted + numColumns = 0; + + } finally + { + if (!wasAlreadyLocked) + { + LOCK.writeLock().unlock(); + } + } + } + + /** + * Insert [start, range] at the region at index i in hiddenColumns, if + * feasible + * + * @param i + * index to insert at + * @param start + * start of range to insert + * @param end + * end of range to insert + * @return true if range was successfully inserted + */ + private boolean insertRangeAtRegion(int i, int start, int end) + { + boolean added = false; + + int[] region = hiddenColumns.get(i); + if (end < region[0] - 1) + { + /* + * insert discontiguous preceding range + */ + hiddenColumns.add(i, new int[] { start, end }); + added = true; + } + else if (end <= region[1]) + { + /* + * new range overlaps existing, or is contiguous preceding it - adjust + * start column + */ + region[0] = Math.min(region[0], start); + added = true; + } + else 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) + { + 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); + + // in theory this is faster than hiddenColumns.remove(i+1) + // benchmarking results a bit ambivalent + hiddenColumns.subList(i + 1, i + 2).clear(); + } + added = true; + } + return added; + } + + /** + * hide a list of ranges + * + * @param ranges + */ + public void hideList(List ranges) + { + try + { + LOCK.writeLock().lock(); + for (int[] r : ranges) + { + hideColumns(r[0], r[1]); + } + cursor.resetCursor(hiddenColumns); + numColumns = 0; + } finally + { + LOCK.writeLock().unlock(); + } + } + + /** + * Unhides, and adds to the selection list, all hidden columns + */ + public void revealAllHiddenColumns(ColumnSelection sel) + { + try + { + LOCK.writeLock().lock(); + if (hiddenColumns != null) + { + Iterator it = hiddenColumns.iterator(); + while (it.hasNext()) + { + int[] region = it.next(); + for (int j = region[0]; j < region[1] + 1; j++) + { + sel.addElement(j); + } + } + hiddenColumns = null; + cursor.resetCursor(hiddenColumns); + numColumns = 0; + } + } finally + { + LOCK.writeLock().unlock(); + } + } + + /** + * Reveals, and marks as selected, the hidden column range with the given + * start column + * + * @param start + * the start column to look for + * @param sel + * the column selection to add the hidden column range to + */ + public void revealHiddenColumns(int start, ColumnSelection sel) + { + try + { + LOCK.writeLock().lock(); + + if (hiddenColumns != null) + { + int regionIndex = cursor.findRegionForColumn(start) + .getRegionIndex(); + + if (regionIndex != -1 && regionIndex != hiddenColumns.size()) + { + // regionIndex is the region which either contains start + // or lies to the right of start + int[] region = hiddenColumns.get(regionIndex); + if (start == region[0]) + { + for (int j = region[0]; j < region[1] + 1; j++) + { + sel.addElement(j); + } + int colsToRemove = region[1] - region[0] + 1; + hiddenColumns.remove(regionIndex); + + if (hiddenColumns.isEmpty()) + { + hiddenColumns = null; + numColumns = 0; + } + else + { + numColumns -= colsToRemove; + } + cursor.updateForDeletedRegion(hiddenColumns); + } + } + } + } finally + { + LOCK.writeLock().unlock(); + } + } + + /** + * Add gaps into the sequences aligned to profileseq under the given + * AlignmentView + * + * @param profileseq + * sequence in al which sequences are aligned to + * @param al + * alignment to have gaps inserted into it + * @param input + * alignment view where sequence corresponding to profileseq is first + * entry + * @return new HiddenColumns for new alignment view, with insertions into + * profileseq marked as hidden. + */ + public static HiddenColumns propagateInsertions(SequenceI profileseq, + AlignmentI al, AlignmentView input) + { + int profsqpos = 0; + + char gc = al.getGapCharacter(); + Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc); + HiddenColumns nview = (HiddenColumns) alandhidden[1]; + SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos]; + nview.propagateInsertions(profileseq, al, origseq); + return nview; + } + + /** + * + * @param profileseq + * sequence in al which corresponds to origseq + * @param al + * alignment which is to have gaps inserted into it + * @param origseq + * sequence corresponding to profileseq which defines gap map for + * modifying al + */ + private void propagateInsertions(SequenceI profileseq, AlignmentI al, + SequenceI origseq) + { + try + { + LOCK.writeLock().lock(); + + char gc = al.getGapCharacter(); + + // 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.hideColumns(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 = hiddenColumns.iterator(); + List 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]) + { + gapPosition++; + if (gaps.get(gapPosition)) + { + numGapsBefore++; + } + } + + 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; s < right - left + 1; s++) + { + sb.append(gc); + } + padGaps(sb, left, profileseq, al); + + } + hiddenColumns = newhidden; + cursor.resetCursor(hiddenColumns); + numColumns = 0; + } finally + { + LOCK.writeLock().unlock(); + } + } + + /** + * Pad gaps in all sequences in alignment except profileseq + * + * @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 void padGaps(StringBuffer sb, int pos, SequenceI profileseq, + AlignmentI al) + { + // loop over the sequences and pad with gaps where required + 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() <= 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 + { + al.getSequenceAt(s).setSequence( + sq.substring(0, pos) + sb.toString() + sq.substring(pos)); + } + } + } + } + + /* + * Methods which only need read access to the hidden columns collection. + * These methods should use a readLock to prevent other threads changing + * the hidden columns collection while it is in use. + */ + + /** * Output regions data as a string. String is in the format: * reg0[0]reg0[1]reg1[0]reg1[1] ... regn[1] * @@ -167,17 +616,23 @@ public class HiddenColumns try { LOCK.readLock().lock(); - int size = 0; - if (hiddenColumns != null) + + if (numColumns == 0 && hiddenColumns != null) { + // numColumns is out of date, so recalculate + int size = 0; + Iterator it = hiddenColumns.iterator(); while (it.hasNext()) { int[] range = it.next(); size += range[1] - range[0] + 1; } + + numColumns = size; } - return size; + + return numColumns; } finally { LOCK.readLock().unlock(); @@ -257,7 +712,7 @@ public class HiddenColumns * int column index in alignment view (count from zero) * @return alignment column index for column */ - public int adjustForHiddenColumns(int column) + public int visibleToAbsoluteColumn(int column) { try { @@ -266,18 +721,7 @@ public class HiddenColumns if (hiddenColumns != null) { - result += cursor.getHiddenOffset(column); - - /* - Iterator it = hiddenColumns.iterator(); - while (it.hasNext()) - { - int[] region = it.next(); - if (result >= region[0]) - { - result += region[1] - region[0] + 1; - } - }*/ + result += cursor.findRegionForVisColumn(column).getHiddenSoFar(); } return result; @@ -290,55 +734,52 @@ public class HiddenColumns /** * Use this method to find out where a column will appear in the visible * alignment when hidden columns exist. If the column is not visible, then the - * left-most visible column will always be returned. + * index of the next visible column on the left will be returned (or 0 if + * there is no visible column on the left) * * @param hiddenColumn * the column index in the full alignment including hidden columns * @return the position of the column in the visible alignment */ - public int findColumnPosition(int hiddenColumn) + public int absoluteToVisibleColumn(int hiddenColumn) { try { LOCK.readLock().lock(); int result = hiddenColumn; - int[] region = null; + if (hiddenColumns != null) { - Iterator it = new RegionsIterator(0, - hiddenColumn, hiddenColumns, cursor); - while (it.hasNext()) - { - region = it.next(); - if (hiddenColumn > region[1]) - { - result -= region[1] + 1 - region[0]; - } - } - - if (region != null && hiddenColumn >= region[0] - && hiddenColumn <= region[1]) + HiddenCursorPosition cursorPos = cursor + .findRegionForColumn(hiddenColumn); + int index = cursorPos.getRegionIndex(); + int hiddenBeforeCol = cursorPos.getHiddenSoFar(); + + // just subtract hidden cols count - this works fine if column is + // visible + result = hiddenColumn - hiddenBeforeCol; + + // now check in case column is hidden - it will be in the returned + // hidden region + if (index < hiddenColumns.size()) { - // Here the hidden column is within a region, so - // we want to return the position of region[0]-1, adjusted for any - // earlier hidden columns. - // Calculate the difference between the actual hidden col position - // and region[0]-1, and then subtract from result to convert result - // from the adjusted hiddenColumn value to the adjusted region[0]-1 - // value. - - // However, if the region begins at 0 we cannot return region[0]-1 - // just return 0 - if (region[0] == 0) - { - return 0; - } - else + int[] region = hiddenColumns.get(index); + if (hiddenColumn >= region[0] && hiddenColumn <= region[1]) { - return result - (hiddenColumn - region[0] + 1); + // actually col is hidden, return region[0]-1 + // unless region[0]==0 in which case return 0 + if (region[0] == 0) + { + result = 0; + } + else + { + result = region[0] - 1 - hiddenBeforeCol; + } } } } + return result; // return the shifted position after removing hidden // columns. } finally @@ -366,7 +807,7 @@ public class HiddenColumns int distance = visibleDistance; // in case startColumn is in a hidden region, move it to the left - int start = adjustForHiddenColumns(findColumnPosition(startColumn)); + int start = visibleToAbsoluteColumn(absoluteToVisibleColumn(startColumn)); Iterator it = new ReverseRegionsIterator(0, start, hiddenColumns); @@ -390,7 +831,6 @@ public class HiddenColumns } } } - return start - distance; } finally @@ -404,7 +844,10 @@ public class HiddenColumns * hidden columns. In otherwords, the next hidden column. * * @param alPos - * the (visible) alignmentPosition to find the next hidden column for + * the absolute (visible) alignmentPosition to find the next hidden + * column for + * @return the index of the next hidden column, or alPos if there is no next + * hidden column */ public int getHiddenBoundaryRight(int alPos) { @@ -413,14 +856,22 @@ public class HiddenColumns LOCK.readLock().lock(); if (hiddenColumns != null) { - Iterator it = hiddenColumns.iterator(); - while (it.hasNext()) + int index = cursor.findRegionForColumn(alPos).getRegionIndex(); + if (index < hiddenColumns.size()) { - int[] region = it.next(); + int[] region = hiddenColumns.get(index); if (alPos < region[0]) { return region[0]; } + else if ((alPos <= region[1]) + && (index + 1 < hiddenColumns.size())) + { + // alPos is within a hidden region, return the next one + // if there is one + region = hiddenColumns.get(index + 1); + return region[0]; + } } } return alPos; @@ -435,8 +886,8 @@ public class HiddenColumns * hidden columns. In otherwords, the previous hidden column. * * @param alPos - * the (visible) alignmentPosition to find the previous hidden column - * for + * the absolute (visible) alignmentPosition to find the previous + * hidden column for */ public int getHiddenBoundaryLeft(int alPos) { @@ -444,17 +895,16 @@ public class HiddenColumns { LOCK.readLock().lock(); - Iterator it = new ReverseRegionsIterator(0, alPos, - hiddenColumns); - while (it.hasNext()) + if (hiddenColumns != null) { - int[] region = it.next(); - if (alPos > region[1]) + int index = cursor.findRegionForColumn(alPos).getRegionIndex(); + + if (index > 0) { + int[] region = hiddenColumns.get(index - 1); return region[1]; } } - return alPos; } finally { @@ -462,132 +912,6 @@ public class HiddenColumns } } - /** - * Adds the specified column range to the hidden columns collection - * - * @param start - * start of range to add (absolute position in alignment) - * @param end - * end of range to add (absolute position in alignment) - */ - public void hideColumns(int start, int end) - { - boolean wasAlreadyLocked = false; - try - { - // 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(); - } - else - { - wasAlreadyLocked = true; - } - - if (hiddenColumns == null) - { - hiddenColumns = new ArrayList<>(); - } - - /* - * new range follows everything else; check first to avoid looping over whole hiddenColumns collection - */ - if (hiddenColumns.isEmpty() - || start > hiddenColumns.get(hiddenColumns.size() - 1)[1]) - { - hiddenColumns.add(new int[] { start, end }); - } - else - { - /* - * traverse existing hidden ranges and insert / amend / append as - * appropriate - */ - boolean added = false; - for (int i = 0; !added && i < hiddenColumns.size(); i++) - { - added = insertRangeAtRegion(i, start, end); - } // for - } - if (!wasAlreadyLocked) - { - cursor.resetCursor(hiddenColumns); - } - } finally - { - if (!wasAlreadyLocked) - { - LOCK.writeLock().unlock(); - } - } - } - - /** - * Insert [start, range] at the region at index i in hiddenColumns, if - * feasible - * - * @param i - * index to insert at - * @param start - * start of range to insert - * @param end - * end of range to insert - * @return true if range was successfully inserted - */ - private boolean insertRangeAtRegion(int i, int start, int end) - { - boolean added = false; - - int[] region = hiddenColumns.get(i); - if (end < region[0] - 1) - { - /* - * insert discontiguous preceding range - */ - hiddenColumns.add(i, new int[] { start, end }); - added = true; - } - else if (end <= region[1]) - { - /* - * new range overlaps existing, or is contiguous preceding it - adjust - * start column - */ - region[0] = Math.min(region[0], start); - added = true; - } - else 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) - { - 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.subList(i + 1, i + 2).clear(); - } - added = true; - } - return added; - } /** * Answers if a column in the alignment is visible @@ -602,18 +926,19 @@ public class HiddenColumns { LOCK.readLock().lock(); - Iterator it = new RegionsIterator(column, column, - hiddenColumns, cursor); - while (it.hasNext()) + int regionindex = cursor.findRegionForColumn(column).getRegionIndex(); + if (regionindex > -1 && regionindex < hiddenColumns.size()) { - int[] region = it.next(); - if (column >= region[0] && column <= region[1]) + int[] region = hiddenColumns.get(regionindex); + // already know that column <= region[1] as cursor returns containing + // region or region to right + if (column >= region[0]) { return false; } } - return true; + } finally { LOCK.readLock().unlock(); @@ -739,7 +1064,7 @@ public class HiddenColumns if (foundStart) { - return findColumnPosition(start); + return absoluteToVisibleColumn(start); } // otherwise, sequence was completely hidden return visPrev; @@ -757,8 +1082,12 @@ public class HiddenColumns */ public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation) { - makeVisibleAnnotation(0, alignmentAnnotation.annotations.length, + if (alignmentAnnotation != null + && alignmentAnnotation.annotations != null) + { + makeVisibleAnnotation(0, alignmentAnnotation.annotations.length, alignmentAnnotation); + } } /** @@ -782,7 +1111,8 @@ public class HiddenColumns int startFrom = start; int endAt = end; - if (alignmentAnnotation.annotations != null) + if (alignmentAnnotation != null + && alignmentAnnotation.annotations != null) { if (hiddenColumns != null && hiddenColumns.size() > 0) { @@ -821,325 +1151,79 @@ public class HiddenColumns if (blocks.hasNext()) { // copy just the visible segment of the annotation row - copylength = annotationLength; - } - else - { - if (annotationLength + block[0] <= alignmentAnnotation.annotations.length) - { - // copy just the visible segment of the annotation row - copylength = annotationLength; - } - else - { - // copy to the end of the annotation row - copylength = alignmentAnnotation.annotations.length - block[0]; - } - } - - els = new Annotation[annotationLength]; - annels.add(els); - System.arraycopy(alignmentAnnotation.annotations, block[0], els, 0, - copylength); - w += annotationLength; - } - - if (w != 0) - { - alignmentAnnotation.annotations = new Annotation[w]; - - w = 0; - for (Annotation[] chnk : annels) - { - System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w, - chnk.length); - w += chnk.length; - } - } - } - - /** - * - * @return true if there are columns hidden - */ - public boolean hasHiddenColumns() - { - try - { - LOCK.readLock().lock(); - return hiddenColumns != null && hiddenColumns.size() > 0; - } finally - { - LOCK.readLock().unlock(); - } - } - - /** - * - * @return true if there are more than one set of columns hidden - */ - public boolean hasManyHiddenColumns() - { - try - { - LOCK.readLock().lock(); - return hiddenColumns != null && hiddenColumns.size() > 1; - } finally - { - LOCK.readLock().unlock(); - } - } - - /** - * mark the columns corresponding to gap characters as hidden in the column - * selection - * - * @param sr - */ - public void hideInsertionsFor(SequenceI sr) - { - try - { - LOCK.writeLock().lock(); - List inserts = sr.getInsertions(); - for (int[] r : inserts) - { - hideColumns(r[0], r[1]); - } - cursor.resetCursor(hiddenColumns); - } finally - { - LOCK.writeLock().unlock(); - } - } - - /** - * Unhides, and adds to the selection list, all hidden columns - */ - public void revealAllHiddenColumns(ColumnSelection sel) - { - try - { - LOCK.writeLock().lock(); - Iterator it = hiddenColumns.iterator(); - while (it.hasNext()) - { - int[] region = it.next(); - for (int j = region[0]; j < region[1] + 1; j++) - { - sel.addElement(j); - } - } - hiddenColumns = null; - cursor.resetCursor(hiddenColumns); - } finally - { - LOCK.writeLock().unlock(); - } - } - - /** - * Reveals, and marks as selected, the hidden column range with the given - * start column - * - * @param start - */ - public void revealHiddenColumns(int start, ColumnSelection sel) - { - try - { - LOCK.writeLock().lock(); - Iterator it = new RegionsIterator(start, start, hiddenColumns, - cursor); - while (it.hasNext()) + copylength = annotationLength; + } + else { - int[] region = it.next(); - if (start == region[0]) + if (annotationLength + block[0] <= alignmentAnnotation.annotations.length) { - for (int j = region[0]; j < region[1] + 1; j++) - { - sel.addElement(j); - } - it.remove(); - break; + // copy just the visible segment of the annotation row + copylength = annotationLength; } - else if (start < region[0]) + else { - break; // passed all possible matching regions + // copy to the end of the annotation row + copylength = alignmentAnnotation.annotations.length - block[0]; } } + + els = new Annotation[annotationLength]; + annels.add(els); + System.arraycopy(alignmentAnnotation.annotations, block[0], els, 0, + copylength); + w += annotationLength; + } + + if (w != 0) + { + alignmentAnnotation.annotations = new Annotation[w]; - if (hiddenColumns.size() == 0) + w = 0; + for (Annotation[] chnk : annels) { - hiddenColumns = null; + System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w, + chnk.length); + w += chnk.length; } - cursor.resetCursor(hiddenColumns); - } finally - { - LOCK.writeLock().unlock(); } } /** - * Add gaps into the sequences aligned to profileseq under the given - * AlignmentView - * - * @param profileseq - * @param al - * - alignment to have gaps inserted into it - * @param input - * - alignment view where sequence corresponding to profileseq is - * first entry - * @return new HiddenColumns for new alignment view, with insertions into - * profileseq marked as hidden. - */ - public static HiddenColumns propagateInsertions(SequenceI profileseq, - AlignmentI al, AlignmentView input) - { - int profsqpos = 0; - - char gc = al.getGapCharacter(); - Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc); - HiddenColumns nview = (HiddenColumns) alandhidden[1]; - SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos]; - nview.propagateInsertions(profileseq, al, origseq); - return nview; - } - - /** * - * @param profileseq - * - sequence in al which corresponds to origseq - * @param al - * - alignment which is to have gaps inserted into it - * @param origseq - * - sequence corresponding to profileseq which defines gap map for - * modifying al + * @return true if there are columns hidden */ - private void propagateInsertions(SequenceI profileseq, AlignmentI al, - SequenceI origseq) + public boolean hasHiddenColumns() { try { - LOCK.writeLock().lock(); - - char gc = al.getGapCharacter(); - - // 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 = hiddenColumns.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]) - { - gapPosition++; - if (gaps.get(gapPosition)) - { - numGapsBefore++; - } - } - - 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; s < right - left + 1; s++) - { - sb.append(gc); - } - padGaps(sb, left, profileseq, al); + LOCK.readLock().lock(); - } - hiddenColumns = newhidden; - cursor.resetCursor(hiddenColumns); + // we don't use getSize()>0 here because it has to iterate over + // the full hiddenColumns collection and so will be much slower + return hiddenColumns != null && hiddenColumns.size() > 0; } finally { - LOCK.writeLock().unlock(); + LOCK.readLock().unlock(); } } /** - * Pad gaps in all sequences in alignment except profileseq * - * @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 + * @return true if there is more than one hidden column region */ - private void padGaps(StringBuffer sb, int pos, SequenceI profileseq, - AlignmentI al) + public boolean hasMultiHiddenColumnRegions() { - // loop over the sequences and pad with gaps where required - for (int s = 0, ns = al.getHeight(); s < ns; s++) + try { - SequenceI sqobj = al.getSequenceAt(s); - if (sqobj != profileseq) - { - 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 - { - al.getSequenceAt(s).setSequence( - sq.substring(0, pos) + sb.toString() + sq.substring(pos)); - } - } + LOCK.readLock().lock(); + return hiddenColumns != null && hiddenColumns.size() > 1; + } finally + { + LOCK.readLock().unlock(); } } + /** * Returns a hashCode built from hidden column ranges */ @@ -1170,7 +1254,7 @@ public class HiddenColumns * @param inserts * - columns map to bits starting from zero */ - public void hideMarkedBits(BitSet inserts) + public void hideColumns(BitSet inserts) { try { @@ -1183,6 +1267,7 @@ public class HiddenColumns hideColumns(firstSet, lastSet - 1); } cursor.resetCursor(hiddenColumns); + numColumns = 0; } finally { LOCK.writeLock().unlock(); @@ -1247,7 +1332,7 @@ public class HiddenColumns higestRange = (range[1] >= endPos) ? range : higestRange; } - if (lowestRange[0] == -1 && lowestRange[1] == -1) + if (lowestRange[0] == -1) // includes (lowestRange[1] == -1) { startPos = alignmentStartEnd[0]; } @@ -1256,7 +1341,7 @@ public class HiddenColumns startPos = lowestRange[1] + 1; } - if (higestRange[0] == -1 && higestRange[1] == -1) + if (higestRange[0] == -1) // includes (higestRange[1] == -1) { endPos = alignmentStartEnd[1]; } @@ -1276,28 +1361,37 @@ public class HiddenColumns * * @param res * visible residue position, unadjusted for hidden columns - * @return region as [start,end] or null if no matching region is found + * @return region as [start,end] or null if no matching region is found. If + * res is adjacent to two regions, returns the left region. */ public int[] getRegionWithEdgeAtRes(int res) { try { LOCK.readLock().lock(); - int adjres = adjustForHiddenColumns(res); + int adjres = visibleToAbsoluteColumn(res); int[] reveal = null; - Iterator it = new RegionsIterator(adjres - 2, - adjres + 2, hiddenColumns, cursor); - while (it.hasNext()) + + if (hiddenColumns != null) { - int[] region = it.next(); - if (adjres + 1 == region[0] || adjres - 1 == region[1]) + // look for a region ending just before adjres + int regionindex = cursor.findRegionForColumn(adjres - 1) + .getRegionIndex(); + if (regionindex < hiddenColumns.size() + && hiddenColumns.get(regionindex)[1] == adjres - 1) { - reveal = region; - break; + reveal = hiddenColumns.get(regionindex); + } + // check if the region ends just after adjres + else if (regionindex < hiddenColumns.size() + && hiddenColumns.get(regionindex)[0] == adjres + 1) + { + reveal = hiddenColumns.get(regionindex); } } return reveal; + } finally { LOCK.readLock().unlock(); @@ -1312,7 +1406,7 @@ public class HiddenColumns try { LOCK.readLock().lock(); - return new BoundedHiddenColsIterator(hiddenColumns); + return new HiddenColsIterator(hiddenColumns); } finally { LOCK.readLock().unlock(); @@ -1333,7 +1427,7 @@ public class HiddenColumns try { LOCK.readLock().lock(); - return new BoundedHiddenColsIterator(start, end, hiddenColumns); + return new HiddenColsIterator(start, end, hiddenColumns); } finally { LOCK.readLock().unlock(); @@ -1354,7 +1448,20 @@ public class HiddenColumns try { LOCK.readLock().lock(); - return new BoundedStartRegionIterator(start, end, hiddenColumns); + + // get absolute position of column in alignment + int absoluteStart = visibleToAbsoluteColumn(start); + + // Get cursor position and supply it to the iterator: + // Since we want visible region start, we look for a cursor for the + // (absoluteStart-1), then if absoluteStart is the start of a visible + // region we'll get the cursor pointing to the region before, which is + // what we want + HiddenCursorPosition pos = cursor + .findRegionForColumn(absoluteStart - 1); + + return new BoundedStartRegionIterator(pos, start, end, + hiddenColumns); } finally { LOCK.readLock().unlock(); @@ -1387,192 +1494,31 @@ public class HiddenColumns * boundaries * * @param start - * (first column inclusive from 0) - * @param end - * (last column - not inclusive) - */ - public Iterator getVisContigsIterator(int start, int end) - { - try - { - LOCK.readLock().lock(); - return new VisibleContigsIterator(start, end, hiddenColumns); - } finally - { - LOCK.readLock().unlock(); - } - } - - /** - * return an iterator over visible segments between the given start and end - * boundaries - * - * @param start - * (first column - inclusive from 0) + * first column, inclusive from 0 * @param end - * (last column - inclusive) + * last column - not inclusive * @param useVisibleCoords * if true, start and end are visible column positions, not absolute - * positions + * positions* */ - public Iterator getVisibleBlocksIterator(int start, int end, + public Iterator getVisContigsIterator(int start, int end, boolean useVisibleCoords) { + int adjstart = start; + int adjend = end; if (useVisibleCoords) { - // TODO - // we should really just convert start and end here with - // adjustForHiddenColumns - // and then create a VisibleContigsIterator - // but without a cursor this will be horribly slow in some situations - // ... so until then... - return new VisibleBlocksVisBoundsIterator(start, end, true); - } - else - { - try - { - LOCK.readLock().lock(); - return new VisibleContigsIterator(start, end + 1, hiddenColumns); - } finally - { - LOCK.readLock().unlock(); - } - } - } - - /** - * 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 - { - private List 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()); + adjstart = visibleToAbsoluteColumn(start); + adjend = visibleToAbsoluteColumn(end); } - @Override - public int[] next() + try { - int[] result = vcontigs.get(currentPosition); - currentPosition++; - return result; - } - - public boolean endsAtHidden() + LOCK.readLock().lock(); + return new VisibleContigsIterator(adjstart, adjend, hiddenColumns); + } finally { - return endsAtHidden; + LOCK.readLock().unlock(); } } }