X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fdatamodel%2FHiddenColumns.java;h=93791e44f8691559f2895f28ceff4f1b32c2de6c;hb=d4e62e67024759a6452c10521a113f578832b9c0;hp=0a4d04b4d4acb2207088d7e1e9f1a737b052012d;hpb=f149fa7699eddc4968dc5a9fa35694abcc28b0cd;p=jalview.git diff --git a/src/jalview/datamodel/HiddenColumns.java b/src/jalview/datamodel/HiddenColumns.java index 0a4d04b..93791e4 100644 --- a/src/jalview/datamodel/HiddenColumns.java +++ b/src/jalview/datamodel/HiddenColumns.java @@ -26,14 +26,42 @@ 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. + * + * @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; /* @@ -49,29 +77,454 @@ public class HiddenColumns { } - /** - * Copy constructor - * - * @param copy - */ - public HiddenColumns(HiddenColumns copy) - { - try - { - LOCK.writeLock().lock(); - if (copy != null) + /* + * 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(); + numColumns = 0; + if (copy != null) + { + if (copy.hiddenColumns != null) + { + hiddenColumns = new ArrayList<>(); + Iterator it = copy.iterator(); + while (it.hasNext()) + { + int[] region = it.next(); + hiddenColumns.add(region); + numColumns += region[1] - region[0] + 1; + } + cursor.resetCursor(hiddenColumns); + } + } + } 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<>(); + numColumns = 0; + 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 }); + numColumns += region[1] - region[0] + 1; + } + } + cursor.resetCursor(hiddenColumns); + } + } finally + { + LOCK.writeLock().unlock(); + } + } + + /** + * 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; + } + + /** + * 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); + 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.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()) { - if (copy.hiddenColumns != null) + // 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]) { - hiddenColumns = new ArrayList<>(); - Iterator it = copy.iterator(); - while (it.hasNext()) + gapPosition++; + if (gaps.get(gapPosition)) { - hiddenColumns.add(it.next()); + numGapsBefore++; } - cursor.resetCursor(hiddenColumns); } + + 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(); @@ -79,51 +532,66 @@ public class HiddenColumns } /** - * 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 + * 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 */ - public HiddenColumns(HiddenColumns copy, int start, int end, int offset) + private void padGaps(StringBuffer sb, int pos, SequenceI profileseq, + AlignmentI al) { - try + // loop over the sequences and pad with gaps where required + for (int s = 0, ns = al.getHeight(); s < ns; s++) { - LOCK.writeLock().lock(); - if (copy != null) + SequenceI sqobj = al.getSequenceAt(s); + if (sqobj != profileseq) { - hiddenColumns = new ArrayList<>(); - numColumns = 0; - Iterator it = copy.getBoundedIterator(start, end); - while (it.hasNext()) + String sq = al.getSequenceAt(s).getSequenceAsString(); + if (sq.length() <= pos) { - 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) + // pad sequence + int diff = pos - sq.length() - 1; + if (diff > 0) { - hiddenColumns.add( - new int[] - { region[0] - offset, region[1] - offset }); - numColumns += region[1] - region[0] + 1; + // 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)); } - cursor.resetCursor(hiddenColumns); } - } finally - { - LOCK.writeLock().unlock(); } } + /* + * 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] @@ -176,15 +644,14 @@ public class HiddenColumns { // numColumns is out of date, so recalculate int size = 0; - if (hiddenColumns != null) + + Iterator it = hiddenColumns.iterator(); + while (it.hasNext()) { - Iterator it = hiddenColumns.iterator(); - while (it.hasNext()) - { - int[] range = it.next(); - size += range[1] - range[0] + 1; - } + int[] range = it.next(); + size += range[1] - range[0] + 1; } + numColumns = size; } @@ -268,7 +735,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 { @@ -277,7 +744,7 @@ public class HiddenColumns if (hiddenColumns != null) { - result += cursor.getHiddenOffset(column).getHiddenSoFar(); + result += cursor.findRegionForVisColumn(column).getHiddenSoFar(); } return result; @@ -290,13 +757,14 @@ 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 { @@ -362,7 +830,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); @@ -386,7 +854,6 @@ public class HiddenColumns } } } - return start - distance; } finally @@ -402,6 +869,8 @@ public class HiddenColumns * @param alPos * 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) { @@ -461,141 +930,12 @@ public class HiddenColumns } return alPos; } finally - { - LOCK.readLock().unlock(); - } - } - - /** - * 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); - - // 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); - hiddenColumns.subList(i + 1, i + 2).clear(); - } - added = true; + { + LOCK.readLock().unlock(); } - return added; } + /** * Answers if a column in the alignment is visible * @@ -613,7 +953,9 @@ public class HiddenColumns if (regionindex > -1 && regionindex < hiddenColumns.size()) { int[] region = hiddenColumns.get(regionindex); - if (column >= region[0] && column <= region[1]) + // already know that column <= region[1] as cursor returns containing + // region or region to right + if (column >= region[0]) { return false; } @@ -745,7 +1087,7 @@ public class HiddenColumns if (foundStart) { - return findColumnPosition(start); + return absoluteToVisibleColumn(start); } // otherwise, sequence was completely hidden return visPrev; @@ -763,8 +1105,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); + } } /** @@ -779,388 +1125,128 @@ public class HiddenColumns * the annotation to operate on */ public void makeVisibleAnnotation(int start, int end, - AlignmentAnnotation alignmentAnnotation) - { - try - { - LOCK.readLock().lock(); - - int startFrom = start; - int endAt = end; - - if (alignmentAnnotation.annotations != null) - { - if (hiddenColumns != null && hiddenColumns.size() > 0) - { - removeHiddenAnnotation(startFrom, endAt, alignmentAnnotation); - } - else - { - alignmentAnnotation.restrict(startFrom, endAt); - } - } - } finally - { - LOCK.readLock().unlock(); - } - } - - private void removeHiddenAnnotation(int start, int end, - AlignmentAnnotation alignmentAnnotation) - { - // mangle the alignmentAnnotation annotation array - ArrayList annels = new ArrayList<>(); - Annotation[] els = null; - - int w = 0; - - Iterator blocks = new VisibleContigsIterator(start, end + 1, - hiddenColumns); - - int copylength; - int annotationLength; - while (blocks.hasNext()) - { - int[] block = blocks.next(); - annotationLength = block[1] - block[0] + 1; - - 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); - 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 - */ - 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 - * @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) + AlignmentAnnotation alignmentAnnotation) { 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); + LOCK.readLock().lock(); - // 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 startFrom = start; + int endAt = end; - int numGapsBefore = 0; - int gapPosition = 0; - while (regions.hasNext()) + if (alignmentAnnotation != null + && alignmentAnnotation.annotations != null) { - // 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]) + if (hiddenColumns != null && hiddenColumns.size() > 0) { - gapPosition++; - if (gaps.get(gapPosition)) - { - numGapsBefore++; - } + removeHiddenAnnotation(startFrom, endAt, alignmentAnnotation); } - - 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++) + else { - sb.append(gc); + alignmentAnnotation.restrict(startFrom, endAt); } - padGaps(sb, left, profileseq, al); - } - hiddenColumns = newhidden; - cursor.resetCursor(hiddenColumns); - numColumns = 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 - */ - private void padGaps(StringBuffer sb, int pos, SequenceI profileseq, - AlignmentI al) + private void removeHiddenAnnotation(int start, int end, + AlignmentAnnotation alignmentAnnotation) { - // loop over the sequences and pad with gaps where required - for (int s = 0, ns = al.getHeight(); s < ns; s++) + // mangle the alignmentAnnotation annotation array + ArrayList annels = new ArrayList<>(); + Annotation[] els = null; + + int w = 0; + + Iterator blocks = new VisibleContigsIterator(start, end + 1, + hiddenColumns); + + int copylength; + int annotationLength; + while (blocks.hasNext()) { - SequenceI sqobj = al.getSequenceAt(s); - if (sqobj != profileseq) + int[] block = blocks.next(); + annotationLength = block[1] - block[0] + 1; + + if (blocks.hasNext()) { - String sq = al.getSequenceAt(s).getSequenceAsString(); - if (sq.length() <= pos) + // copy just the visible segment of the annotation row + copylength = annotationLength; + } + else + { + if (annotationLength + block[0] <= alignmentAnnotation.annotations.length) { - // 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(); + // copy just the visible segment of the annotation row + copylength = annotationLength; } else { - al.getSequenceAt(s).setSequence( - sq.substring(0, pos) + sb.toString() + sq.substring(pos)); + // 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(); + + // 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.readLock().unlock(); + } + } + + /** + * + * @return true if there is more than one hidden column region + */ + public boolean hasMultiHiddenColumnRegions() + { + try + { + LOCK.readLock().lock(); + return hiddenColumns != null && hiddenColumns.size() > 1; + } finally + { + LOCK.readLock().unlock(); } } + /** * Returns a hashCode built from hidden column ranges */ @@ -1269,7 +1355,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]; } @@ -1278,7 +1364,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]; } @@ -1305,7 +1391,7 @@ public class HiddenColumns try { LOCK.readLock().lock(); - int adjres = adjustForHiddenColumns(res); + int adjres = visibleToAbsoluteColumn(res); int[] reveal = null; @@ -1325,16 +1411,6 @@ public class HiddenColumns { reveal = hiddenColumns.get(regionindex); } - // or try the next region - else - { - regionindex++; - if (regionindex < hiddenColumns.size() - && hiddenColumns.get(regionindex)[0] == adjres + 1) - { - reveal = hiddenColumns.get(regionindex); - } - } } return reveal; @@ -1352,7 +1428,7 @@ public class HiddenColumns try { LOCK.readLock().lock(); - return new BoundedHiddenColsIterator(hiddenColumns); + return new HiddenColsIterator(hiddenColumns); } finally { LOCK.readLock().unlock(); @@ -1373,7 +1449,7 @@ public class HiddenColumns try { LOCK.readLock().lock(); - return new BoundedHiddenColsIterator(start, end, hiddenColumns); + return new HiddenColsIterator(start, end, hiddenColumns); } finally { LOCK.readLock().unlock(); @@ -1394,7 +1470,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(); @@ -1427,192 +1516,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 + 1) - { - // 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(); } } }