Merge branch 'develop' into feature/JAL-2759
authorkiramt <k.mourao@dundee.ac.uk>
Wed, 8 Nov 2017 16:06:14 +0000 (16:06 +0000)
committerkiramt <k.mourao@dundee.ac.uk>
Wed, 8 Nov 2017 16:06:14 +0000 (16:06 +0000)
Conflicts:
src/jalview/gui/SeqCanvas.java
src/jalview/renderer/ScaleRenderer.java
test/jalview/datamodel/SequenceTest.java

40 files changed:
benchmarking/README
benchmarking/src/main/java/org/jalview/HiddenColsIteratorsBenchmark.java [new file with mode: 0644]
benchmarking/src/main/java/org/jalview/HiddenColumnsBenchmark.java
src/jalview/analysis/Dna.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AnnotationColumnChooser.java
src/jalview/appletgui/AnnotationLabels.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/datamodel/BoundedHiddenColsIterator.java [new file with mode: 0644]
src/jalview/datamodel/BoundedStartRegionIterator.java [new file with mode: 0644]
src/jalview/datamodel/CigarArray.java
src/jalview/datamodel/HiddenColumns.java
src/jalview/datamodel/HiddenColumnsCursor.java [new file with mode: 0644]
src/jalview/datamodel/RegionsIterator.java [new file with mode: 0644]
src/jalview/datamodel/ReverseRegionsIterator.java [new file with mode: 0644]
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/datamodel/VisibleColsCollection.java
src/jalview/datamodel/VisibleColsIterator.java
src/jalview/datamodel/VisibleContigsIterator.java [new file with mode: 0644]
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AnnotationColumnChooser.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/VamsasApplication.java
src/jalview/renderer/ScaleRenderer.java
src/jalview/util/MappingUtils.java
test/jalview/analysis/DnaTest.java
test/jalview/datamodel/ColumnSelectionTest.java
test/jalview/datamodel/HiddenColumnsTest.java
test/jalview/datamodel/SequenceTest.java
test/jalview/datamodel/VisibleColsIteratorTest.java
test/jalview/gui/AlignFrameTest.java
test/jalview/gui/AnnotationColumnChooserTest.java [new file with mode: 0644]
test/jalview/io/JSONFileTest.java
test/jalview/util/MappingUtilsTest.java

index 60b94a9..93b9820 100644 (file)
@@ -22,4 +22,7 @@ to install the jalview.jar file in the local maven repository. The pom.xml in th
   To get JSON output instead use:
   java -jar target/benchmarks.jar -rf json
   
+  To run a specific benchmark file use:
+  java -jar target/benchmarks.jar <Benchmark class name> 
+  
   JSON output can be viewed quickly by drag-dropping on http://jmh.morethan.io/
\ No newline at end of file
diff --git a/benchmarking/src/main/java/org/jalview/HiddenColsIteratorsBenchmark.java b/benchmarking/src/main/java/org/jalview/HiddenColsIteratorsBenchmark.java
new file mode 100644 (file)
index 0000000..6d5863b
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+
+package org.jalview;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Param;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
+
+/*
+ * A class to benchmark hidden columns performance
+ */
+@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Fork(1)
+public class HiddenColsIteratorsBenchmark {
+       /*
+        * State with multiple hidden columns and a start position set
+        */
+       @State(Scope.Thread)
+       public static class HiddenColsAndStartState
+       {
+               @Param({"300", "10000", "100000"})
+               public int maxcols;
+               
+               @Param({"1", "50", "90"})
+               public int startpcnt; // position as percentage of maxcols
+               
+               @Param({"1","15","100"})
+               public int hide;
+               
+               HiddenColumns h = new HiddenColumns();
+               Random rand = new Random();
+               
+               public int hiddenColumn;
+               public int visibleColumn;
+       
+               @Setup
+               public void setup()
+               {
+                       rand.setSeed(1234);
+                       int lastcol = 0;
+               while (lastcol < maxcols)
+               {
+                       int count = rand.nextInt(100); 
+                       lastcol += count;
+                       h.hideColumns(lastcol, lastcol+hide);
+                       lastcol+=hide;
+               }
+               
+               // make sure column at start is hidden
+               hiddenColumn = (int)(maxcols * startpcnt/100.0);
+               h.hideColumns(hiddenColumn, hiddenColumn);
+               
+               // and column <hide> after start is visible
+               ColumnSelection sel = new ColumnSelection();
+               h.revealHiddenColumns(hiddenColumn+hide, sel);
+               visibleColumn = hiddenColumn+hide;
+               
+               System.out.println("Maxcols: " + maxcols + " HiddenCol: " + hiddenColumn + " Hide: " + hide);
+               System.out.println("Number of hidden columns: " + h.getSize());
+               }
+       }
+       
+       /* Convention: functions in alphabetical order */
+       
+       @Benchmark
+       @BenchmarkMode({Mode.Throughput})
+       public int benchStartIterator(HiddenColsAndStartState tstate)
+       {
+               int res = 0;
+               int startx = tstate.visibleColumn;
+               Iterator<Integer> it = tstate.h.getBoundedStartIterator(startx,
+                               startx+60);
+        while (it.hasNext())
+        {
+          res = it.next() - startx;
+          Blackhole.consumeCPU(5);
+        }
+        return res;
+       }
+       
+       @Benchmark
+       @BenchmarkMode({Mode.Throughput})
+       public int benchBoundedIterator(HiddenColsAndStartState tstate)
+       {
+               int startx = tstate.visibleColumn;
+               int blockStart = startx;
+               int blockEnd;
+               int screenY = 0;
+               Iterator<int[]> it = tstate.h.getBoundedIterator(startx,
+                               startx+60);
+        while (it.hasNext())
+        {
+               int[] region = it.next();
+          
+               blockEnd = Math.min(region[0] - 1, blockStart + 60 - screenY);
+             
+               screenY += blockEnd - blockStart + 1;
+               blockStart = region[1] + 1;
+               
+               Blackhole.consumeCPU(5);
+        }
+        return blockStart;
+       }
+}
index eb35e3b..6e4742a 100644 (file)
@@ -114,21 +114,6 @@ public class HiddenColumnsBenchmark
        
        @Benchmark
        @BenchmarkMode({Mode.Throughput})
-       public List<Integer> benchFindHiddenRegionPositions(HiddenColsAndStartState tstate)
-       {
-               return tstate.h.findHiddenRegionPositions();
-       }
-       
-       @Benchmark
-       @BenchmarkMode({Mode.Throughput})
-       public ArrayList<int[]> benchGetHiddenColumnsCopy(HiddenColsAndStartState tstate)
-       {
-               return tstate.h.getHiddenColumnsCopy();
-       }
-       
-       
-       @Benchmark
-       @BenchmarkMode({Mode.Throughput})
     public int benchGetSize(HiddenColsAndStartState tstate)
     {
                return tstate.h.getSize();
index a10b037..d534c8f 100644 (file)
@@ -44,6 +44,7 @@ import jalview.util.ShiftList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Iterator;
 import java.util.List;
 
 public class Dna
@@ -56,19 +57,23 @@ public class Dna
    * 'final' variables describe the inputs to the translation, which should not
    * be modified.
    */
-  final private List<SequenceI> selection;
+  private final List<SequenceI> selection;
 
-  final private String[] seqstring;
+  private final String[] seqstring;
 
-  final private int[] contigs;
+  private final Iterator<int[]> contigs;
 
-  final private char gapChar;
+  private final char gapChar;
 
-  final private AlignmentAnnotation[] annotations;
+  private final AlignmentAnnotation[] annotations;
 
-  final private int dnaWidth;
+  private final int dnaWidth;
 
-  final private AlignmentI dataset;
+  private final AlignmentI dataset;
+
+  private ShiftList vismapping;
+
+  private int[] startcontigs;
 
   /*
    * Working variables for the translation.
@@ -91,7 +96,7 @@ public class Dna
    * @param viewport
    * @param visibleContigs
    */
-  public Dna(AlignViewportI viewport, int[] visibleContigs)
+  public Dna(AlignViewportI viewport, Iterator<int[]> visibleContigs)
   {
     this.selection = Arrays.asList(viewport.getSequenceSelection());
     this.seqstring = viewport.getViewAsString(true);
@@ -100,6 +105,45 @@ public class Dna
     this.annotations = viewport.getAlignment().getAlignmentAnnotation();
     this.dnaWidth = viewport.getAlignment().getWidth();
     this.dataset = viewport.getAlignment().getDataset();
+    initContigs();
+  }
+
+  /**
+   * Initialise contigs used as starting point for translateCodingRegion
+   */
+  private void initContigs()
+  {
+    vismapping = new ShiftList(); // map from viscontigs to seqstring
+    // intervals
+
+    int npos = 0;
+    int[] lastregion = null;
+    ArrayList<Integer> tempcontigs = new ArrayList<>();
+    while (contigs.hasNext())
+    {
+      int[] region = contigs.next();
+      if (lastregion == null)
+      {
+        vismapping.addShift(npos, region[0]);
+      }
+      else
+      {
+        // hidden region
+        vismapping.addShift(npos, region[0] - lastregion[1] + 1);
+      }
+      lastregion = region;
+      tempcontigs.add(region[0]);
+      tempcontigs.add(region[1]);
+    }
+
+    startcontigs = new int[tempcontigs.size()];
+    int i = 0;
+    for (Integer val : tempcontigs)
+    {
+      startcontigs[i] = val;
+      i++;
+    }
+    tempcontigs = null;
   }
 
   /**
@@ -161,7 +205,7 @@ public class Dna
 
     int s;
     int sSize = selection.size();
-    List<SequenceI> pepseqs = new ArrayList<SequenceI>();
+    List<SequenceI> pepseqs = new ArrayList<>();
     for (s = 0; s < sSize; s++)
     {
       SequenceI newseq = translateCodingRegion(selection.get(s),
@@ -213,7 +257,7 @@ public class Dna
       if (dnarefs != null)
       {
         // intersect with pep
-        List<DBRefEntry> mappedrefs = new ArrayList<DBRefEntry>();
+        List<DBRefEntry> mappedrefs = new ArrayList<>();
         DBRefEntry[] refs = dna.getDBRefs();
         for (int d = 0; d < refs.length; d++)
         {
@@ -391,27 +435,13 @@ public class Dna
           String seqstring, AlignedCodonFrame acf,
           List<SequenceI> proteinSeqs)
   {
-    List<int[]> skip = new ArrayList<int[]>();
-    int skipint[] = null;
-    ShiftList vismapping = new ShiftList(); // map from viscontigs to seqstring
-    // intervals
-    int vc;
-    int[] scontigs = new int[contigs.length];
+    List<int[]> skip = new ArrayList<>();
+    int[] skipint = null;
     int npos = 0;
-    for (vc = 0; vc < contigs.length; vc += 2)
-    {
-      if (vc == 0)
-      {
-        vismapping.addShift(npos, contigs[vc]);
-      }
-      else
-      {
-        // hidden region
-        vismapping.addShift(npos, contigs[vc] - contigs[vc - 1] + 1);
-      }
-      scontigs[vc] = contigs[vc];
-      scontigs[vc + 1] = contigs[vc + 1];
-    }
+    int vc = 0;
+
+    int[] scontigs = new int[startcontigs.length];
+    System.arraycopy(startcontigs, 0, scontigs, 0, startcontigs.length);
 
     // allocate a roughly sized buffer for the protein sequence
     StringBuilder protein = new StringBuilder(seqstring.length() / 2);
@@ -800,7 +830,7 @@ public class Dna
   public AlignmentI reverseCdna(boolean complement)
   {
     int sSize = selection.size();
-    List<SequenceI> reversed = new ArrayList<SequenceI>();
+    List<SequenceI> reversed = new ArrayList<>();
     for (int s = 0; s < sSize; s++)
     {
       SequenceI newseq = reverseSequence(selection.get(s).getName(),
index ef87671..73cabc4 100644 (file)
@@ -1905,7 +1905,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
   static StringBuffer copiedSequences;
 
-  static Vector<int[]> copiedHiddenColumns;
+  static HiddenColumns copiedHiddenColumns;
 
   protected void copy_actionPerformed()
   {
@@ -1929,14 +1929,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
     if (viewport.hasHiddenColumns() && viewport.getSelectionGroup() != null)
     {
-      copiedHiddenColumns = new Vector<>(viewport.getAlignment()
-              .getHiddenColumns().getHiddenColumnsCopy());
       int hiddenOffset = viewport.getSelectionGroup().getStartRes();
-      for (int[] region : copiedHiddenColumns)
-      {
-        region[0] = region[0] - hiddenOffset;
-        region[1] = region[1] - hiddenOffset;
-      }
+      int hiddenCutoff = viewport.getSelectionGroup().getEndRes();
+
+      // create new HiddenColumns object with copy of hidden regions
+      // between startRes and endRes, offset by startRes
+      copiedHiddenColumns = new HiddenColumns(
+              viewport.getAlignment().getHiddenColumns(), hiddenOffset,
+              hiddenCutoff, hiddenOffset);
     }
     else
     {
@@ -2043,14 +2043,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
         }
         AlignFrame af = new AlignFrame(new Alignment(newSeqs),
                 viewport.applet, newtitle, false);
-        if (copiedHiddenColumns != null)
-        {
-          for (int i = 0; i < copiedHiddenColumns.size(); i++)
-          {
-            int[] region = copiedHiddenColumns.elementAt(i);
-            af.viewport.hideColumns(region[0], region[1]);
-          }
-        }
+        af.viewport.setHiddenColumns(copiedHiddenColumns);
 
         jalview.bin.JalviewLite.addFrame(af, newtitle, frameWidth,
                 frameHeight);
index 7674de7..206b132 100644 (file)
@@ -46,7 +46,6 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.TextEvent;
 import java.awt.event.TextListener;
-import java.util.ArrayList;
 import java.util.Vector;
 
 //import javax.swing.JPanel;
@@ -293,16 +292,6 @@ public class AnnotationColumnChooser extends AnnotationRowFilter implements
       {
         HiddenColumns oldHidden = av.getAnnotationColumnSelectionState()
                 .getOldHiddenColumns();
-        if (oldHidden != null)
-        {
-          ArrayList<int[]> regions = oldHidden.getHiddenColumnsCopy();
-          for (int[] positions : regions)
-          {
-            av.hideColumns(positions[0], positions[1]);
-          }
-        }
-        // TODO not clear why we need to hide all the columns (above) if we are
-        // going to copy the hidden columns over wholesale anyway
         av.getAlignment().setHiddenColumns(oldHidden);
       }
       av.sendSelection();
index d8f65a5..6e3257d 100755 (executable)
@@ -23,6 +23,7 @@ package jalview.appletgui;
 import jalview.analysis.AlignmentUtils;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.util.MessageManager;
@@ -50,7 +51,6 @@ import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Vector;
 
 public class AnnotationLabels extends Panel
         implements ActionListener, MouseListener, MouseMotionListener
@@ -843,8 +843,8 @@ public class AnnotationLabels extends Panel
                     + "\t" + sq.getSequenceAsString() + "\n");
     if (av.hasHiddenColumns())
     {
-      jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector<>(
-              av.getAlignment().getHiddenColumns().getHiddenColumnsCopy());
+      jalview.appletgui.AlignFrame.copiedHiddenColumns = new HiddenColumns(
+              av.getAlignment().getHiddenColumns());
     }
   }
 
index 7d4150d..b65af2e 100755 (executable)
@@ -42,6 +42,7 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.beans.PropertyChangeEvent;
+import java.util.Iterator;
 import java.util.List;
 
 public class ScalePanel extends Panel
@@ -436,24 +437,17 @@ public class ScalePanel extends Panel
       if (av.getShowHiddenMarkers())
       {
         int widthx = 1 + endx - startx;
-        List<Integer> positions = hidden.findHiddenRegionPositions();
-        for (int pos : positions)
+        Iterator<Integer> it = hidden.getBoundedStartIterator(startx,
+                startx + widthx + 1);
+        while (it.hasNext())
         {
-
-          res = pos - startx;
-
-          if (res < 0 || res > widthx)
-          {
-            continue;
-          }
+          res = it.next() - startx;
 
           gg.fillPolygon(
                   new int[]
-                  { -1 + res * avCharWidth - avcharHeight / 4,
-                      -1 + res * avCharWidth + avcharHeight / 4,
-                      -1 + res * avCharWidth },
-                  new int[]
-                  { y, y, y + 2 * yOf }, 3);
+                  { -1 + res * avCharWidth - avcharHeight / 4, -1 + res * avCharWidth + avcharHeight / 4,
+              -1 + res * avCharWidth }, new int[]
+          { y, y, y + 2 * yOf }, 3);
         }
       }
     }
index f59967d..b499a74 100755 (executable)
@@ -22,6 +22,7 @@ package jalview.appletgui;
 
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.HiddenColumns.VisibleBlocksVisBoundsIterator;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -37,7 +38,7 @@ import java.awt.Graphics;
 import java.awt.Image;
 import java.awt.Panel;
 import java.beans.PropertyChangeEvent;
-import java.util.List;
+import java.util.Iterator;
 
 public class SeqCanvas extends Panel implements ViewportListenerI
 {
@@ -417,71 +418,71 @@ public class SeqCanvas extends Panel implements ViewportListenerI
           int canvasHeight, int startRes)
   {
     AlignmentI al = av.getAlignment();
-
+  
     FontMetrics fm = getFontMetrics(av.getFont());
-
+  
     LABEL_EAST = 0;
     LABEL_WEST = 0;
-
+  
     if (av.getScaleRightWrapped())
     {
       LABEL_EAST = fm.stringWidth(getMask());
     }
-
+  
     if (av.getScaleLeftWrapped())
     {
       LABEL_WEST = fm.stringWidth(getMask());
     }
-
+  
     int hgap = avcharHeight;
     if (av.getScaleAboveWrapped())
     {
       hgap += avcharHeight;
     }
-
+  
     int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth;
     int cHeight = av.getAlignment().getHeight() * avcharHeight;
-
+  
     av.setWrappedWidth(cWidth);
-
+  
     av.getRanges().setViewportStartAndWidth(startRes, cWidth);
-
+  
     int endx;
     int ypos = hgap;
-
+  
     int maxwidth = av.getAlignment().getWidth();
-
+  
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
               .findColumnPosition(maxwidth);
     }
-
+  
     while ((ypos <= canvasHeight) && (startRes < maxwidth))
     {
       endx = startRes + cWidth - 1;
-
+  
       if (endx > maxwidth)
       {
         endx = maxwidth;
       }
-
+  
       g.setColor(Color.black);
-
+  
       if (av.getScaleLeftWrapped())
       {
         drawWestScale(g, startRes, endx, ypos);
       }
-
+  
       if (av.getScaleRightWrapped())
       {
         g.translate(canvasWidth - LABEL_EAST, 0);
         drawEastScale(g, startRes, endx, ypos);
         g.translate(-(canvasWidth - LABEL_EAST), 0);
       }
-
+  
       g.translate(LABEL_WEST, 0);
-
+  
       if (av.getScaleAboveWrapped())
       {
         drawNorthScale(g, startRes, endx, ypos);
@@ -491,37 +492,27 @@ public class SeqCanvas extends Panel implements ViewportListenerI
         HiddenColumns hidden = av.getAlignment().getHiddenColumns();
         g.setColor(Color.blue);
         int res;
-        List<Integer> positions = hidden.findHiddenRegionPositions();
-        for (int pos : positions)
+        Iterator<Integer> it = hidden.getBoundedStartIterator(startRes,
+                endx + 1);
+        while (it.hasNext())
         {
-          res = pos - startRes;
-
-          if (res < 0 || res > endx - startRes)
-          {
-            continue;
-          }
-
+          res = it.next() - startRes;
           gg.fillPolygon(
                   new int[]
-                  { res * avcharWidth - avcharHeight / 4,
-                      res * avcharWidth + avcharHeight / 4,
-                      res * avcharWidth },
+                  { res * avcharWidth - avcharHeight / 4, res * avcharWidth + avcharHeight / 4, res * avcharWidth },
                   new int[]
-                  { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2),
-                      ypos - (avcharHeight / 2) + 8 },
-                  3);
-
+                  { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2), ypos - (avcharHeight / 2) + 8 }, 3);
         }
       }
-
+  
       if (g.getClip() == null)
       {
         g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
       }
-
+  
       drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
       g.setClip(null);
-
+  
       if (av.isShowAnnotation())
       {
         g.translate(0, cHeight + ypos + 4);
@@ -529,17 +520,17 @@ public class SeqCanvas extends Panel implements ViewportListenerI
         {
           annotations = new AnnotationPanel(av);
         }
-
+  
         annotations.drawComponent(g, startRes, endx + 1);
         g.translate(0, -cHeight - ypos - 4);
       }
       g.translate(-LABEL_WEST, 0);
-
+  
       ypos += cHeight + getAnnotationHeight() + hgap;
-
+  
       startRes += cWidth;
     }
-
+  
   }
 
   AnnotationPanel annotations;
@@ -570,70 +561,44 @@ public class SeqCanvas extends Panel implements ViewportListenerI
     else
     {
       int screenY = 0;
-      final int screenYMax = endRes - startRes;
-      int blockStart = startRes;
-      int blockEnd = endRes;
-
-      if (av.hasHiddenColumns())
-      {
-        HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-        for (int[] region : hidden.getHiddenColumnsCopy())
-        {
-          int hideStart = region[0];
-          int hideEnd = region[1];
-
-          if (hideStart <= blockStart)
-          {
-            blockStart += (hideEnd - hideStart) + 1;
-            continue;
-          }
-
-          /*
-           * draw up to just before the next hidden region, or the end of
-           * the visible region, whichever comes first
-           */
-          blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
-                  - screenY);
-
-          g1.translate(screenY * avcharWidth, 0);
+      int blockStart;
+      int blockEnd;
 
-          draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
+      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+      VisibleBlocksVisBoundsIterator regions = (VisibleBlocksVisBoundsIterator) hidden
+              .getVisibleBlocksIterator(startRes, endRes, true);
 
-          /*
-           * draw the downline of the hidden column marker (ScalePanel draws the
-           * triangle on top) if we reached it
-           */
-          if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
-          {
-            g1.setColor(Color.blue);
-            g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
-                    0 + offset,
-                    (blockEnd - blockStart + 1) * avcharWidth - 1,
-                    (endSeq - startSeq + 1) * avcharHeight + offset);
-          }
-
-          g1.translate(-screenY * avcharWidth, 0);
-          screenY += blockEnd - blockStart + 1;
-          blockStart = hideEnd + 1;
-
-          if (screenY > screenYMax)
-          {
-            // already rendered last block
-            return;
-          }
-        }
-      }
-      if (screenY <= screenYMax)
+      while (regions.hasNext())
       {
-        // remaining visible region to render
-        blockEnd = blockStart + (endRes - startRes) - screenY;
+        int[] region = regions.next();
+        blockEnd = region[1];
+        blockStart = region[0];
+
+        /*
+         * draw up to just before the next hidden region, or the end of
+         * the visible region, whichever comes first
+         */
         g1.translate(screenY * avcharWidth, 0);
+
         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
 
+        /*
+         * draw the downline of the hidden column marker (ScalePanel draws the
+         * triangle on top) if we reached it
+         */
+        if (av.getShowHiddenMarkers()
+                && (regions.hasNext() || regions.endsAtHidden()))
+        {
+          g1.setColor(Color.blue);
+          g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
+                  0 + offset, (blockEnd - blockStart + 1) * avcharWidth - 1,
+                  (endSeq - startSeq + 1) * avcharHeight + offset);
+        }
+
         g1.translate(-screenY * avcharWidth, 0);
+        screenY += blockEnd - blockStart + 1;
       }
     }
-
   }
 
   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
diff --git a/src/jalview/datamodel/BoundedHiddenColsIterator.java b/src/jalview/datamodel/BoundedHiddenColsIterator.java
new file mode 100644 (file)
index 0000000..aa3f4ad
--- /dev/null
@@ -0,0 +1,111 @@
+package jalview.datamodel;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An iterator which iterates over hidden column regions in a range. Works with
+ * a copy of the hidden columns collection. Intended to be used by callers
+ * OUTSIDE of HiddenColumns.
+ */
+public class BoundedHiddenColsIterator implements Iterator<int[]>
+{
+  // start position to iterate from
+  private int start;
+
+  // end position to iterate to
+  private int end;
+
+  // current index in hiddenColumns
+  private int currentPosition = 0;
+
+  // current column in hiddenColumns
+  private int[] currentRegion;
+
+  // local copy or reference to hiddenColumns
+  private List<int[]> localHidden;
+
+  /**
+   * Unbounded constructor
+   */
+  BoundedHiddenColsIterator(List<int[]> hiddenColumns)
+  {
+    if (hiddenColumns != null)
+    {
+      int last = hiddenColumns.get(hiddenColumns.size() - 1)[1];
+      init(0, last, hiddenColumns);
+    }
+    else
+    {
+      init(0, 0, hiddenColumns);
+    }
+  }
+
+  /**
+   * Construct an iterator over hiddenColums bounded at [lowerBound,upperBound]
+   * 
+   * @param lowerBound
+   *          lower bound to iterate from
+   * @param upperBound
+   *          upper bound to iterate to
+   */
+  BoundedHiddenColsIterator(int lowerBound, int upperBound,
+          List<int[]> hiddenColumns)
+  {
+    init(lowerBound, upperBound, hiddenColumns);
+  }
+
+  /**
+   * Construct an iterator over hiddenColums bounded at [lowerBound,upperBound]
+   * 
+   * @param lowerBound
+   *          lower bound to iterate from
+   * @param upperBound
+   *          upper bound to iterate to
+   */
+  private void init(int lowerBound, int upperBound,
+          List<int[]> hiddenColumns)
+  {
+    start = lowerBound;
+    end = upperBound;
+
+    if (hiddenColumns != null)
+    {
+      localHidden = new ArrayList<>();
+
+      // iterate until a region overlaps with [start,end]
+      int i = 0;
+      while ((i < hiddenColumns.size())
+              && (hiddenColumns.get(i)[1] < start))
+      {
+        i++;
+      }
+
+      // iterate from start to end, adding each hidden region. Positions are
+      // absolute, and all regions which *overlap* [start,end] are added.
+      while (i < hiddenColumns.size() && (hiddenColumns.get(i)[0] <= end))
+      {
+        int[] rh = hiddenColumns.get(i);
+        int[] cp = new int[2];
+        System.arraycopy(rh, 0, cp, 0, rh.length);
+        localHidden.add(cp);
+        i++;
+      }
+    }
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return (localHidden != null) && (currentPosition < localHidden.size());
+  }
+
+  @Override
+  public int[] next()
+  {
+    currentRegion = localHidden.get(currentPosition);
+    currentPosition++;
+    return currentRegion;
+  }
+}
diff --git a/src/jalview/datamodel/BoundedStartRegionIterator.java b/src/jalview/datamodel/BoundedStartRegionIterator.java
new file mode 100644 (file)
index 0000000..831e906
--- /dev/null
@@ -0,0 +1,94 @@
+package jalview.datamodel;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An iterator which iterates over visible start positions of hidden column
+ * regions in a range.
+ */
+public class BoundedStartRegionIterator implements Iterator<Integer>
+{
+  // start position to iterate from
+  private int start;
+
+  // end position to iterate to
+  private int end;
+
+  // current index in hiddenColumns
+  private int currentPosition = 0;
+
+  // local copy or reference to hiddenColumns
+  private List<Integer> positions = null;
+
+  /**
+   * Construct an iterator over hiddenColums bounded at [lowerBound,upperBound]
+   * 
+   * @param lowerBound
+   *          lower bound to iterate from
+   * @param upperBound
+   *          upper bound to iterate to
+   * @param useCopyCols
+   *          whether to make a local copy of hiddenColumns for iteration (set
+   *          to true if calling from outwith the HiddenColumns class)
+   */
+  BoundedStartRegionIterator(int lowerBound, int upperBound,
+          List<int[]> hiddenColumns)
+  {
+    start = lowerBound;
+    end = upperBound;
+
+    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<>();
+    }
+
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return (currentPosition < positions.size());
+  }
+
+  /**
+   * Get next hidden region start position
+   * 
+   * @return the start position in *visible* coordinates
+   */
+  @Override
+  public Integer next()
+  {
+    int result = positions.get(currentPosition);
+    currentPosition++;
+    return result;
+  }
+}
+
index 1723f1d..17e9ea6 100644 (file)
@@ -20,7 +20,7 @@
  */
 package jalview.datamodel;
 
-import java.util.List;
+import java.util.Iterator;
 
 public class CigarArray extends CigarBase
 {
@@ -90,9 +90,7 @@ public class CigarArray extends CigarBase
           SequenceGroup selectionGroup)
   {
     this(constructSeqCigarArray(alignment, selectionGroup));
-    constructFromAlignment(alignment,
-            hidden != null ? hidden.getHiddenColumnsCopy() : null,
-            selectionGroup);
+    constructFromAlignment(alignment, hidden, selectionGroup);
   }
 
   private static int[] _calcStartEndBounds(AlignmentI alignment,
@@ -154,33 +152,25 @@ public class CigarArray extends CigarBase
    * @param selectionGroup
    */
   private void constructFromAlignment(AlignmentI alignment,
-          List<int[]> list, SequenceGroup selectionGroup)
+          HiddenColumns hidden, SequenceGroup selectionGroup)
   {
     int[] _startend = _calcStartEndBounds(alignment, selectionGroup);
-    int start = _startend[1], end = _startend[2];
+    int start = _startend[1];
+    int end = _startend[2];
     // now construct the CigarArray operations
-    if (list != null)
+    if (hidden != null)
     {
       int[] region;
-      int hideStart, hideEnd;
+      int hideStart;
+      int hideEnd;
       int last = start;
-      for (int j = 0; last < end & j < list.size(); j++)
+
+      Iterator<int[]> regions = hidden.getBoundedIterator(start, end);
+      while (regions.hasNext())
       {
-        region = list.get(j);
+        region = regions.next();
         hideStart = region[0];
         hideEnd = region[1];
-        // edit hidden regions to selection range
-
-        // just move on if hideEnd is before last
-        if (hideEnd < last)
-        {
-          continue;
-        }
-        // exit if next region is after end
-        if (hideStart > end)
-        {
-          break;
-        }
 
         // truncate region at start if last falls in region
         if ((hideStart < last) && (hideEnd >= last))
@@ -204,6 +194,7 @@ public class CigarArray extends CigarBase
         addOperation(CigarArray.D, 1 + hideEnd - hideStart);
         last = hideEnd + 1;
       }
+
       // Final match if necessary.
       if (last <= end)
       {
index c0a43ee..b86a1a1 100644 (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.Vector;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 public class HiddenColumns
 {
+  private static final int HASH_MULTIPLIER = 31;
+
   private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
 
+  private HiddenColumnsCursor cursor = new HiddenColumnsCursor();
+
   /*
    * list of hidden column [start, end] ranges; the list is maintained in
    * ascending start column order
@@ -61,7 +61,13 @@ public class HiddenColumns
       {
         if (copy.hiddenColumns != null)
         {
-          hiddenColumns = copy.copyHiddenRegionsToArrayList();
+          hiddenColumns = new ArrayList<>();
+          Iterator<int[]> it = copy.iterator();
+          while (it.hasNext())
+          {
+            hiddenColumns.add(it.next());
+          }
+          cursor.resetCursor(hiddenColumns);
         }
       }
     } finally
@@ -71,16 +77,47 @@ public class HiddenColumns
   }
 
   /**
-   * 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.
+   * 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
    * 
-   * @return empty list or List of hidden column intervals
    */
-  private List<int[]> getHiddenRegions()
+  public HiddenColumns(HiddenColumns copy, int start, int end, int offset)
   {
-    return hiddenColumns == null ? Collections.<int[]> emptyList()
-            : hiddenColumns;
+    try
+    {
+      LOCK.writeLock().lock();
+      if (copy != null)
+      {
+        hiddenColumns = new ArrayList<>();
+        Iterator<int[]> 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 });
+          }
+        }
+        cursor.resetCursor(hiddenColumns);
+      }
+    } finally
+    {
+      LOCK.writeLock().unlock();
+    }
   }
 
   /**
@@ -101,13 +138,17 @@ public class HiddenColumns
       StringBuilder regionBuilder = new StringBuilder();
       if (hiddenColumns != null)
       {
-        for (int[] range : hiddenColumns)
+        Iterator<int[]> it = hiddenColumns.iterator();
+        while (it.hasNext())
         {
+          int[] range = it.next();
           regionBuilder.append(delimiter).append(range[0]).append(between)
                   .append(range[1]);
+          if (!it.hasNext())
+          {
+            regionBuilder.deleteCharAt(0);
+          }
         }
-
-        regionBuilder.deleteCharAt(0);
       }
       return regionBuilder.toString();
     } finally
@@ -127,10 +168,12 @@ public class HiddenColumns
     {
       LOCK.readLock().lock();
       int size = 0;
-      if (hasHiddenColumns())
+      if (hiddenColumns != null)
       {
-        for (int[] range : hiddenColumns)
+        Iterator<int[]> it = hiddenColumns.iterator();
+        while (it.hasNext())
         {
+          int[] range = it.next();
           size += range[1] - range[0] + 1;
         }
       }
@@ -141,6 +184,28 @@ public class HiddenColumns
     }
   }
 
+  /**
+   * Get the number of distinct hidden regions
+   * 
+   * @return number of regions
+   */
+  public int getNumberOfRegions()
+  {
+    try
+    {
+      LOCK.readLock().lock();
+      int num = 0;
+      if (hasHiddenColumns())
+      {
+        num = hiddenColumns.size();
+      }
+      return num;
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
+  }
+
   @Override
   public boolean equals(Object obj)
   {
@@ -166,10 +231,13 @@ public class HiddenColumns
       {
         return false;
       }
-      int i = 0;
-      for (int[] thisRange : hiddenColumns)
+
+      Iterator<int[]> it = hiddenColumns.iterator();
+      Iterator<int[]> thatit = that.iterator();
+      while (it.hasNext())
       {
-        int[] thatRange = that.hiddenColumns.get(i++);
+        int[] thisRange = it.next();
+        int[] thatRange = thatit.next();
         if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
         {
           return false;
@@ -195,17 +263,23 @@ public class HiddenColumns
     {
       LOCK.readLock().lock();
       int result = column;
+
       if (hiddenColumns != null)
       {
-        for (int i = 0; i < hiddenColumns.size(); i++)
+        result += cursor.getHiddenOffset(column);
+
+
+        /*Iterator<int[]> it = hiddenColumns.iterator();
+        while (it.hasNext())
         {
-          int[] region = hiddenColumns.get(i);
+          int[] region = it.next();
           if (result >= region[0])
           {
             result += region[1] - region[0] + 1;
           }
-        }
+        }*/
       }
+
       return result;
     } finally
     {
@@ -228,29 +302,30 @@ public class HiddenColumns
     {
       LOCK.readLock().lock();
       int result = hiddenColumn;
+      int[] region = null;
       if (hiddenColumns != null)
       {
-        int index = 0;
-        int[] region;
-        do
+        Iterator<int[]> it = new RegionsIterator(0,
+                hiddenColumn, hiddenColumns, cursor);
+        while (it.hasNext())
         {
-          region = hiddenColumns.get(index++);
+          region = it.next();
           if (hiddenColumn > region[1])
           {
             result -= region[1] + 1 - region[0];
           }
-        } while ((hiddenColumn > region[1])
-                && (index < hiddenColumns.size()));
+        }
 
-        if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
+        if (region != null && hiddenColumn >= region[0]
+                && hiddenColumn <= region[1])
         {
           // 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
+          // 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
@@ -287,100 +362,37 @@ public class HiddenColumns
   {
     try
     {
-
       LOCK.readLock().lock();
       int distance = visibleDistance;
 
       // 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;
-      }
-
-      // 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;
-
-        // 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--;
-      }
-
-      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();
-    }
-
-  }
-
-  /**
-   * Use this method to determine the set of hiddenRegion start positions
-   * 
-   * @return list of column number in visible view where hidden regions start
-   */
-  public List<Integer> findHiddenRegionPositions()
-  {
-    try
-    {
-      LOCK.readLock().lock();
-      List<Integer> positions = null;
+      Iterator<int[]> it = new ReverseRegionsIterator(0, start,
+              hiddenColumns);
 
-      if (hiddenColumns != null)
+      while (it.hasNext() && (distance > 0))
       {
-        positions = new ArrayList<>(hiddenColumns.size());
+        int[] region = it.next();
 
-        positions.add(hiddenColumns.get(0)[0]);
-        for (int i = 1; i < hiddenColumns.size(); ++i)
+        if (start > region[1])
         {
-
-          int result = 0;
-          if (hiddenColumns != null)
+          // subtract the gap to right of region from distance
+          if (start - region[1] <= distance)
           {
-            int index = 0;
-            int gaps = 0;
-            do
-            {
-              int[] region = hiddenColumns.get(index);
-              gaps += region[1] + 1 - region[0];
-              result = region[1] + 1;
-              index++;
-            } while (index <= i);
-
-            result -= gaps;
+            distance -= start - region[1];
+            start = region[0] - 1;
+          }
+          else
+          {
+            start = start - distance;
+            distance = 0;
           }
-          positions.add(result);
         }
       }
-      else
-      {
-        positions = new ArrayList<>();
-      }
 
-      return positions;
+      return start - distance;
+
     } finally
     {
       LOCK.readLock().unlock();
@@ -391,8 +403,8 @@ public class HiddenColumns
    * This method returns the rightmost limit of a region of an alignment with
    * hidden columns. In otherwords, the next hidden column.
    * 
-   * @param index
-   *          int
+   * @param alPos
+   *          the (visible) alignmentPosition to find the next hidden column for
    */
   public int getHiddenBoundaryRight(int alPos)
   {
@@ -401,33 +413,30 @@ public class HiddenColumns
       LOCK.readLock().lock();
       if (hiddenColumns != null)
       {
-        int index = 0;
-        do
+        Iterator<int[]> it = hiddenColumns.iterator();
+        while (it.hasNext())
         {
-          int[] region = hiddenColumns.get(index);
+          int[] region = it.next();
           if (alPos < region[0])
           {
             return region[0];
           }
-
-          index++;
-        } while (index < hiddenColumns.size());
+        }
       }
-
       return alPos;
     } finally
     {
       LOCK.readLock().unlock();
     }
-
   }
 
   /**
    * This method returns the leftmost limit of a region of an alignment with
    * hidden columns. In otherwords, the previous hidden column.
    * 
-   * @param index
-   *          int
+   * @param alPos
+   *          the (visible) alignmentPosition to find the previous hidden column
+   *          for
    */
   public int getHiddenBoundaryLeft(int alPos)
   {
@@ -435,19 +444,15 @@ public class HiddenColumns
     {
       LOCK.readLock().lock();
 
-      if (hiddenColumns != null)
+      Iterator<int[]> it = new ReverseRegionsIterator(0, alPos,
+              hiddenColumns);
+      while (it.hasNext())
       {
-        int index = hiddenColumns.size() - 1;
-        do
+        int[] region = it.next();
+        if (alPos > region[1])
         {
-          int[] region = hiddenColumns.get(index);
-          if (alPos > region[1])
-          {
-            return region[1];
-          }
-
-          index--;
-        } while (index > -1);
+          return region[1];
+        }
       }
 
       return alPos;
@@ -458,47 +463,12 @@ public class HiddenColumns
   }
 
   /**
-   * This method returns the index of the hidden region to the left of a column
-   * position. If the column is in a hidden region it returns the index of the
-   * region to the left. If there is no hidden region to the left it returns -1.
-   * 
-   * @param pos
-   *          int
-   */
-  private int getHiddenIndexLeft(int pos)
-  {
-    try
-    {
-
-      LOCK.readLock().lock();
-      if (hiddenColumns != null)
-      {
-        int index = hiddenColumns.size() - 1;
-        do
-        {
-          int[] region = hiddenColumns.get(index);
-          if (pos > region[1])
-          {
-            return index;
-          }
-
-          index--;
-        } while (index > -1);
-      }
-
-      return -1;
-    } finally
-    {
-      LOCK.readLock().unlock();
-    }
-
-  }
-
-  /**
-   * Adds the specified column range to the hidden columns
+   * 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)
   {
@@ -522,66 +492,29 @@ public class HiddenColumns
       }
 
       /*
-       * traverse existing hidden ranges and insert / amend / append as
-       * appropriate
+       * new range follows everything else; check first to avoid looping over whole hiddenColumns collection
        */
-      for (int i = 0; i < hiddenColumns.size(); i++)
+      if (hiddenColumns.isEmpty()
+              || start > hiddenColumns.get(hiddenColumns.size() - 1)[1])
       {
-        int[] region = hiddenColumns.get(i);
-
-        if (end < region[0] - 1)
-        {
-          /*
-           * insert discontiguous preceding range
-           */
-          hiddenColumns.add(i, new int[] { start, end });
-          return;
-        }
-
-        if (end <= region[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)
+        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++)
         {
-          /*
-           * 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.remove(i + 1);
-          }
-          return;
-        }
+          added = insertRangeAtRegion(i, start, end);
+        } // for
+      }
+      if (!wasAlreadyLocked)
+      {
+        cursor.resetCursor(hiddenColumns);
       }
-
-      /*
-       * remaining case is that the new range follows everything else
-       */
-      hiddenColumns.add(new int[] { start, end });
     } finally
     {
       if (!wasAlreadyLocked)
@@ -591,69 +524,96 @@ public class HiddenColumns
     }
   }
 
-  public boolean isVisible(int column)
+  /**
+   * 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)
   {
-    try
-    {
-      LOCK.readLock().lock();
-
-      if (hiddenColumns != null)
-      {
-        for (int[] region : hiddenColumns)
-        {
-          if (column >= region[0] && column <= region[1])
-          {
-            return false;
-          }
-        }
-      }
+    boolean added = false;
 
-      return true;
-    } finally
+    int[] region = hiddenColumns.get(i);
+    if (end < region[0] - 1)
     {
-      LOCK.readLock().unlock();
+      /*
+       * insert discontiguous preceding range
+       */
+      hiddenColumns.add(i, new int[] { start, end });
+      added = true;
     }
-  }
-
-  private ArrayList<int[]> copyHiddenRegionsToArrayList()
-  {
-    int size = 0;
-    if (hiddenColumns != null)
+    else if (end <= region[1])
     {
-      size = hiddenColumns.size();
+      /*
+       * new range overlaps existing, or is contiguous preceding it - adjust
+       * start column
+       */
+      region[0] = Math.min(region[0], start);
+      added = true;
     }
-    ArrayList<int[]> copy = new ArrayList<>(size);
-
-    for (int i = 0, j = size; i < j; i++)
+    else if (start <= region[1] + 1)
     {
-      int[] rh;
-      int[] cp;
-      rh = hiddenColumns.get(i);
-      if (rh != null)
+      /*
+       * 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)
       {
-        cp = new int[rh.length];
-        System.arraycopy(rh, 0, cp, 0, rh.length);
-        copy.add(cp);
+        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 copy;
+    return added;
   }
 
   /**
-   * Returns a copy of the vector of hidden regions, as an ArrayList. Before
-   * using this method please consider if you really need access to the hidden
-   * regions - a new (or existing!) method on HiddenColumns might be more
-   * appropriate.
+   * Answers if a column in the alignment is visible
    * 
-   * @return hidden regions as an ArrayList of [start,end] pairs
+   * @param column
+   *          absolute position of column in the alignment
+   * @return true if column is visible
    */
-  public ArrayList<int[]> getHiddenColumnsCopy()
+  public boolean isVisible(int column)
   {
     try
     {
       LOCK.readLock().lock();
-      return copyHiddenRegionsToArrayList();
+
+      Iterator<int[]> it = new RegionsIterator(column, column,
+              hiddenColumns, cursor);
+      while (it.hasNext())
+      {
+        int[] region = it.next();
+        if (column >= region[0] && column <= region[1])
+        {
+          return false;
+        }
+      }
+
+      return true;
     } finally
     {
       LOCK.readLock().unlock();
@@ -661,332 +621,128 @@ public class HiddenColumns
   }
 
   /**
-   * propagate shift in alignment columns to column selection
+   * Get the visible sections of a set of sequences
    * 
    * @param start
-   *          beginning of edit
-   * @param left
-   *          shift in edit (+ve for removal, or -ve for inserts)
+   *          sequence position to start from
+   * @param end
+   *          sequence position to end at
+   * @param seqs
+   *          an array of sequences
+   * @return an array of strings encoding the visible parts of each sequence
    */
-  public List<int[]> compensateForEdit(int start, int change,
-          ColumnSelection sel)
+  public String[] getVisibleSequenceStrings(int start, int end,
+          SequenceI[] seqs)
   {
     try
     {
-      LOCK.writeLock().lock();
-      List<int[]> deletedHiddenColumns = null;
-
-      if (hiddenColumns != null)
+      LOCK.readLock().lock();
+      int iSize = seqs.length;
+      String[] selections = new String[iSize];
+      if (hiddenColumns != null && hiddenColumns.size() > 0)
       {
-        deletedHiddenColumns = new ArrayList<>();
-        int hSize = hiddenColumns.size();
-        for (int i = 0; i < hSize; i++)
+        for (int i = 0; i < iSize; i++)
         {
-          int[] region = hiddenColumns.get(i);
-          if (region[0] > start && start + change > region[1])
-          {
-            deletedHiddenColumns.add(region);
-
-            hiddenColumns.remove(i);
-            i--;
-            hSize--;
-            continue;
-          }
+          StringBuffer visibleSeq = new StringBuffer();
 
-          if (region[0] > start)
-          {
-            region[0] -= change;
-            region[1] -= change;
-          }
+          Iterator<int[]> blocks = new VisibleContigsIterator(start,
+                  end + 1, hiddenColumns);
 
-          if (region[0] < 0)
+          while (blocks.hasNext())
           {
-            region[0] = 0;
+            int[] block = blocks.next();
+            if (blocks.hasNext())
+            {
+              visibleSeq
+                      .append(seqs[i].getSequence(block[0], block[1] + 1));
+            }
+            else
+            {
+              visibleSeq
+                      .append(seqs[i].getSequence(block[0], block[1]));
+            }
           }
 
+          selections[i] = visibleSeq.toString();
+        }
+      }
+      else
+      {
+        for (int i = 0; i < iSize; i++)
+        {
+          selections[i] = seqs[i].getSequenceAsString(start, end);
         }
-
-        this.revealHiddenColumns(0, sel);
       }
 
-      return deletedHiddenColumns;
+      return selections;
     } finally
     {
-      LOCK.writeLock().unlock();
+      LOCK.readLock().unlock();
     }
   }
 
   /**
-   * propagate shift in alignment columns to column selection special version of
-   * compensateForEdit - allowing for edits within hidden regions
+   * Locate the first position visible for this sequence. If seq isn't visible
+   * then return the position of the left side of the hidden boundary region.
    * 
-   * @param start
-   *          beginning of edit
-   * @param left
-   *          shift in edit (+ve for removal, or -ve for inserts)
+   * @param seq
+   *          sequence to find position for
+   * @return visible start position
    */
-  public void compensateForDelEdits(int start, int change)
+  public int locateVisibleStartOfSequence(SequenceI seq)
   {
     try
     {
-      LOCK.writeLock().lock();
-      if (hiddenColumns != null)
+      LOCK.readLock().lock();
+      int start = 0;
+
+      if (hiddenColumns == null || hiddenColumns.size() == 0)
       {
-        for (int i = 0; i < hiddenColumns.size(); i++)
-        {
-          int[] region = hiddenColumns.get(i);
-          if (region[0] >= start)
-          {
-            region[0] -= change;
-          }
-          if (region[1] >= start)
-          {
-            region[1] -= change;
-          }
-          if (region[1] < region[0])
-          {
-            hiddenColumns.remove(i--);
-          }
+        return seq.findIndex(seq.getStart()) - 1;
+      }
 
-          if (region[0] < 0)
-          {
-            region[0] = 0;
-          }
-          if (region[1] < 0)
-          {
-            region[1] = 0;
-          }
-        }
-      }
-    } finally
-    {
-      LOCK.writeLock().unlock();
-    }
-  }
+      // Simply walk along the sequence whilst watching for hidden column
+      // boundaries
+      Iterator<int[]> regions = hiddenColumns.iterator();
+      int hideStart = seq.getLength();
+      int hideEnd = -1;
+      int visPrev = 0;
+      int visNext = 0;
+      boolean foundStart = false;
 
-  /**
-   * 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<end
-   */
-  public int[] getVisibleContigs(int start, int end)
-  {
-    try
-    {
-      LOCK.readLock().lock();
-      if (hiddenColumns != null && hiddenColumns.size() > 0)
+      // step through the non-gapped positions of the sequence
+      for (int i = seq.getStart(); i <= seq.getEnd() && (!foundStart); i++)
       {
-        List<int[]> visiblecontigs = new ArrayList<>();
-        List<int[]> regions = getHiddenRegions();
-
-        int vstart = start;
-        int[] region;
-        int hideStart;
-        int hideEnd;
+        // get alignment position of this residue in the sequence
+        int p = seq.findIndex(i) - 1;
 
-        for (int j = 0; vstart < end && j < regions.size(); j++)
+        // update hidden region start/end
+        while (hideEnd < p && regions.hasNext())
         {
-          region = regions.get(j);
+          int[] region = regions.next();
+          visPrev = visNext;
+          visNext += region[0] - visPrev;
           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)
+        if (hideEnd < p)
         {
-          visiblecontigs.add(new int[] { vstart, end - 1 });
+          hideStart = seq.getLength();
         }
-        int[] vcontigs = new int[visiblecontigs.size() * 2];
-        for (int i = 0, j = visiblecontigs.size(); i < j; i++)
+        // update visible boundary for sequence
+        if (p < hideStart)
         {
-          int[] vc = visiblecontigs.get(i);
-          visiblecontigs.set(i, null);
-          vcontigs[i * 2] = vc[0];
-          vcontigs[i * 2 + 1] = vc[1];
+          start = p;
+          foundStart = true;
         }
-        visiblecontigs.clear();
-        return vcontigs;
-      }
-      else
-      {
-        return new int[] { start, end - 1 };
       }
-    } finally
-    {
-      LOCK.readLock().unlock();
-    }
-  }
-
-  public String[] getVisibleSequenceStrings(int start, int end,
-          SequenceI[] seqs)
-  {
-    try
-    {
-      LOCK.readLock().lock();
-      int iSize = seqs.length;
-      String[] selections = new String[iSize];
-      if (hiddenColumns != null && hiddenColumns.size() > 0)
-      {
-        for (int i = 0; i < iSize; i++)
-        {
-          StringBuffer visibleSeq = new StringBuffer();
-          List<int[]> regions = getHiddenRegions();
 
-          int blockStart = start;
-          int blockEnd = end;
-          int[] region;
-          int hideStart;
-          int hideEnd;
-
-          for (int j = 0; j < regions.size(); j++)
-          {
-            region = regions.get(j);
-            hideStart = region[0];
-            hideEnd = region[1];
-
-            if (hideStart < start)
-            {
-              continue;
-            }
-
-            blockStart = Math.min(blockStart, hideEnd + 1);
-            blockEnd = Math.min(blockEnd, hideStart);
-
-            if (blockStart > blockEnd)
-            {
-              break;
-            }
-
-            visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
-
-            blockStart = hideEnd + 1;
-            blockEnd = end;
-          }
-
-          if (end > blockStart)
-          {
-            visibleSeq.append(seqs[i].getSequence(blockStart, end));
-          }
-
-          selections[i] = visibleSeq.toString();
-        }
-      }
-      else
-      {
-        for (int i = 0; i < iSize; i++)
-        {
-          selections[i] = seqs[i].getSequenceAsString(start, end);
-        }
-      }
-
-      return selections;
-    } finally
-    {
-      LOCK.readLock().unlock();
-    }
-  }
-
-  /**
-   * Locate the first and last position visible for this sequence. if seq isn't
-   * visible then return the position of the left and right of the hidden
-   * boundary region, and the corresponding alignment column indices for the
-   * extent of the sequence
-   * 
-   * @param seq
-   * @return int[] { visible start, visible end, first seqpos, last seqpos,
-   *         alignment index for seq start, alignment index for seq end }
-   */
-  public int[] locateVisibleBoundsOfSequence(SequenceI seq)
-  {
-    try
-    {
-      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;
-        int ilpos = seq.findIndex(lpos) - 1;
-        return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
-      }
-
-      // Simply walk along the sequence whilst watching for hidden column
-      // boundaries
-      List<int[]> regions = getHiddenRegions();
-      int spos = fpos;
-      int lastvispos = -1;
-      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++)
-      {
-        if (!Comparison.isGap(seq.getCharAt(p)))
-        {
-          // keep track of first/last column
-          // containing sequence data regardless of visibility
-          if (firstP == -1)
-          {
-            firstP = p;
-          }
-          lastP = p;
-          // update hidden region start/end
-          while (hideEnd < p && rcount < regions.size())
-          {
-            int[] region = regions.get(rcount++);
-            visPrev = visNext;
-            visNext += region[0] - visPrev;
-            hideStart = region[0];
-            hideEnd = region[1];
-          }
-          if (hideEnd < p)
-          {
-            hideStart = seq.getLength();
-          }
-          // update visible boundary for sequence
-          if (p < hideStart)
-          {
-            if (!foundStart)
-            {
-              fpos = spos;
-              start = p;
-              foundStart = true;
-            }
-            lastvispos = p;
-            lpos = spos;
-          }
-          // look for next sequence position
-          spos++;
-        }
-      }
       if (foundStart)
       {
-        return new int[] { findColumnPosition(start),
-            findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
+        return findColumnPosition(start);
       }
       // otherwise, sequence was completely hidden
-      return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
+      return visPrev;
     } finally
     {
       LOCK.readLock().unlock();
@@ -1001,7 +757,8 @@ public class HiddenColumns
    */
   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
   {
-    makeVisibleAnnotation(-1, -1, alignmentAnnotation);
+    makeVisibleAnnotation(0, alignmentAnnotation.annotations.length,
+            alignmentAnnotation);
   }
 
   /**
@@ -1021,96 +778,83 @@ public class HiddenColumns
     try
     {
       LOCK.readLock().lock();
-      if (alignmentAnnotation.annotations == null)
-      {
-        return;
-      }
-      if (start == end && end == -1)
-      {
-        start = 0;
-        end = alignmentAnnotation.annotations.length;
-      }
-      if (hiddenColumns != null && hiddenColumns.size() > 0)
-      {
-        // then mangle the alignmentAnnotation annotation array
-        Vector<Annotation[]> annels = new Vector<>();
-        Annotation[] els = null;
-        List<int[]> regions = getHiddenRegions();
-        int blockStart = start;
-        int blockEnd = end;
-        int[] region;
-        int hideStart;
-        int hideEnd;
-        int w = 0;
-
-        for (int j = 0; j < regions.size(); j++)
-        {
-          region = regions.get(j);
-          hideStart = region[0];
-          hideEnd = region[1];
 
-          if (hideStart < start)
-          {
-            continue;
-          }
-
-          blockStart = Math.min(blockStart, hideEnd + 1);
-          blockEnd = Math.min(blockEnd, hideStart);
+      int startFrom = start;
+      int endAt = end;
 
-          if (blockStart > blockEnd)
-          {
-            break;
-          }
-
-          annels.addElement(els = new Annotation[blockEnd - blockStart]);
-          System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
-                  0, els.length);
-          w += els.length;
-          blockStart = hideEnd + 1;
-          blockEnd = end;
-        }
-
-        if (end > blockStart)
+      if (alignmentAnnotation.annotations != null)
+      {
+        if (hiddenColumns != null && hiddenColumns.size() > 0)
         {
-          annels.addElement(els = new Annotation[end - blockStart + 1]);
-          if ((els.length
-                  + blockStart) <= alignmentAnnotation.annotations.length)
-          {
-            // copy just the visible segment of the annotation row
-            System.arraycopy(alignmentAnnotation.annotations, blockStart,
-                    els, 0, els.length);
-          }
-          else
-          {
-            // copy to the end of the annotation row
-            System.arraycopy(alignmentAnnotation.annotations, blockStart,
-                    els, 0,
-                    (alignmentAnnotation.annotations.length - blockStart));
-          }
-          w += els.length;
+          removeHiddenAnnotation(startFrom, endAt, alignmentAnnotation);
         }
-        if (w == 0)
+        else
         {
-          return;
+          alignmentAnnotation.restrict(startFrom, endAt);
         }
+      }
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
+  }
 
-        alignmentAnnotation.annotations = new Annotation[w];
-        w = 0;
-
-        for (Annotation[] chnk : annels)
-        {
-          System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
-                  chnk.length);
-          w += chnk.length;
-        }
+  private void removeHiddenAnnotation(int start, int end,
+          AlignmentAnnotation alignmentAnnotation)
+  {
+    // mangle the alignmentAnnotation annotation array
+    ArrayList<Annotation[]> annels = new ArrayList<>();
+    Annotation[] els = null;
+
+    int w = 0;
+    
+    Iterator<int[]> 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
       {
-        alignmentAnnotation.restrict(start, end);
+        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];
+        }
       }
-    } finally
+      
+      els = new Annotation[annotationLength];
+      annels.add(els);
+      System.arraycopy(alignmentAnnotation.annotations, block[0], els, 0,
+              copylength);
+      w += annotationLength;
+    }
+    
+    if (w != 0)
     {
-      LOCK.readLock().unlock();
+      alignmentAnnotation.annotations = new Annotation[w];
+
+      w = 0;
+      for (Annotation[] chnk : annels)
+      {
+        System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
+                chnk.length);
+        w += chnk.length;
+      }
     }
   }
 
@@ -1162,6 +906,7 @@ public class HiddenColumns
       {
         hideColumns(r[0], r[1]);
       }
+      cursor.resetCursor(hiddenColumns);
     } finally
     {
       LOCK.writeLock().unlock();
@@ -1178,17 +923,18 @@ public class HiddenColumns
       LOCK.writeLock().lock();
       if (hiddenColumns != null)
       {
-        for (int i = 0; i < hiddenColumns.size(); i++)
+        Iterator<int[]> it = hiddenColumns.iterator();
+        while (it.hasNext())
         {
-          int[] region = hiddenColumns.get(i);
+          int[] region = it.next();
           for (int j = region[0]; j < region[1] + 1; j++)
           {
             sel.addElement(j);
           }
         }
+        hiddenColumns = null;
+        cursor.resetCursor(hiddenColumns);
       }
-
-      hiddenColumns = null;
     } finally
     {
       LOCK.writeLock().unlock();
@@ -1206,148 +952,31 @@ public class HiddenColumns
     try
     {
       LOCK.writeLock().lock();
-      for (int i = 0; i < hiddenColumns.size(); i++)
+      Iterator<int[]> it = new RegionsIterator(start, start, hiddenColumns,
+              cursor);
+      while (it.hasNext())
       {
-        int[] region = hiddenColumns.get(i);
+        int[] region = it.next();
         if (start == region[0])
         {
           for (int j = region[0]; j < region[1] + 1; j++)
           {
             sel.addElement(j);
           }
-
-          hiddenColumns.remove(region);
+          it.remove();
           break;
         }
-      }
-      if (hiddenColumns.size() == 0)
-      {
-        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 pruneIntervalList(final List<int[]> shifts,
-          ArrayList<int[]> intervals)
-  {
-    boolean pruned = false;
-    int i = 0;
-    int j = intervals.size() - 1;
-    int s = 0;
-    int t = shifts.size() - 1;
-    int[] hr = intervals.get(i);
-    int[] sr = shifts.get(s);
-    while (i <= j && s <= t)
-    {
-      boolean trailinghn = hr[1] >= sr[0];
-      if (!trailinghn)
-      {
-        if (i < j)
-        {
-          hr = intervals.get(++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.remove(i);
-          pruned = true;
-          j--;
-          if (i <= j)
-          {
-            hr = intervals.get(i);
-          }
-          continue;
-        }
-        if (leadinghc)
+        else if (start < region[0])
         {
-          hr[0] = endshift; // clip c terminal region
-          leadinghn = !leadinghn;
-          pruned = true;
+          break; // passed all possible matching regions
         }
       }
-      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<int[]> shifts)
-  {
-    try
-    {
-      LOCK.writeLock().lock();
-      // delete any intervals intersecting.
-      if (hiddenColumns != null)
+      if (hiddenColumns.size() == 0)
       {
-        pruneIntervalList(shifts, hiddenColumns);
-        if (hiddenColumns != null && hiddenColumns.size() == 0)
-        {
-          hiddenColumns = null;
-        }
+        hiddenColumns = null;
       }
+      cursor.resetCursor(hiddenColumns);
     } finally
     {
       LOCK.writeLock().unlock();
@@ -1393,154 +1022,125 @@ public class HiddenColumns
   private void propagateInsertions(SequenceI profileseq, AlignmentI al,
           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 = getVisibleContigs(0, profileseq.getLength());
-    int spos = 0;
-    int offset = 0;
-
-    // add profile to visible contigs
-    for (int v = 0; v < viscontigs.length; v += 2)
+    try
     {
-      if (viscontigs[v] > spos)
+      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<int[]> regions = hiddenColumns.iterator();
+      ArrayList<int[]> newhidden = new ArrayList<>();
+
+      int numGapsBefore = 0;
+      int gapPosition = 0;
+      while (regions.hasNext())
       {
-        StringBuffer sb = new StringBuffer();
-        for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
+        // 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])
         {
-          sb.append(gc);
-        }
-        for (int s = 0, ns = al.getHeight(); s < ns; s++)
-        {
-          SequenceI sqobj = al.getSequenceAt(s);
-          if (sqobj != profileseq)
+          gapPosition++;
+          if (gaps.get(gapPosition))
           {
-            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));
-            }
+            numGapsBefore++;
           }
         }
-        // offset+=sb.length();
-      }
-      spos = viscontigs[v + 1] + 1;
-    }
-    if ((offset + spos) < profileseq.getLength())
-    {
-      // pad the final region with gaps.
-      StringBuffer sb = new StringBuffer();
-      for (int s = 0, ns = profileseq.getLength() - spos
-              - offset; s < ns; 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)
+
+        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++)
         {
-          // 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();
+          sb.append(gc);
         }
-      }
-    }
-  }
+        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<int[]> shifts = deletions.getShifts();
-      if (shifts != null && shifts.size() > 0)
-      {
-        pruneDeletions(shifts);
-
-        // and shift the rest.
-        this.compensateForEdits(deletions);
       }
+      hiddenColumns = newhidden;
+      cursor.resetCursor(hiddenColumns);
+    } finally
+    {
+      LOCK.writeLock().unlock();
     }
   }
 
   /**
-   * 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<int[]> 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;
   }
 
   /**
@@ -1553,13 +1153,12 @@ public class HiddenColumns
     {
       LOCK.readLock().lock();
       int hashCode = 1;
-      if (hiddenColumns != null)
+      Iterator<int[]> it = hiddenColumns.iterator();
+      while (it.hasNext())
       {
-        for (int[] hidden : hiddenColumns)
-        {
-          hashCode = 31 * hashCode + hidden[0];
-          hashCode = 31 * hashCode + hidden[1];
-        }
+        int[] hidden = it.next();
+        hashCode = HASH_MULTIPLIER * hashCode + hidden[0];
+        hashCode = HASH_MULTIPLIER * hashCode + hidden[1];
       }
       return hashCode;
     } finally
@@ -1586,6 +1185,7 @@ public class HiddenColumns
         lastSet = inserts.nextClearBit(firstSet);
         hideColumns(firstSet, lastSet - 1);
       }
+      cursor.resetCursor(hiddenColumns);
     } finally
     {
       LOCK.writeLock().unlock();
@@ -1606,8 +1206,10 @@ public class HiddenColumns
       {
         return;
       }
-      for (int[] range : hiddenColumns)
+      Iterator<int[]> it = hiddenColumns.iterator();
+      while (it.hasNext())
       {
+        int[] range = it.next();
         inserts.set(range[0], range[1] + 1);
       }
     } finally
@@ -1640,10 +1242,12 @@ public class HiddenColumns
         return new int[] { startPos, endPos };
       }
 
-      for (int[] hiddenCol : hiddenColumns)
+      Iterator<int[]> it = hiddenColumns.iterator();
+      while (it.hasNext())
       {
-        lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
-        higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
+        int[] range = it.next();
+        lowestRange = (range[0] <= startPos) ? range : lowestRange;
+        higestRange = (range[1] >= endPos) ? range : higestRange;
       }
 
       if (lowestRange[0] == -1 && lowestRange[1] == -1)
@@ -1668,7 +1272,6 @@ public class HiddenColumns
     {
       LOCK.readLock().unlock();
     }
-
   }
 
   /**
@@ -1686,15 +1289,15 @@ public class HiddenColumns
       int adjres = adjustForHiddenColumns(res);
 
       int[] reveal = null;
-      if (hiddenColumns != null)
+      Iterator<int[]> it = new RegionsIterator(adjres - 2,
+              adjres + 2, hiddenColumns, cursor);
+      while (it.hasNext())
       {
-        for (int[] region : hiddenColumns)
+        int[] region = it.next();
+        if (adjres + 1 == region[0] || adjres - 1 == region[1])
         {
-          if (adjres + 1 == region[0] || adjres - 1 == region[1])
-          {
-            reveal = region;
-            break;
-          }
+          reveal = region;
+          break;
         }
       }
       return reveal;
@@ -1704,4 +1307,275 @@ public class HiddenColumns
     }
   }
 
+  /**
+   * Return an iterator over the hidden regions
+   */
+  public Iterator<int[]> iterator()
+  {
+    try
+    {
+      LOCK.readLock().lock();
+      return new BoundedHiddenColsIterator(hiddenColumns);
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
+  }
+
+  /**
+   * Return a bounded iterator over the hidden regions
+   * 
+   * @param start
+   *          position to start from (inclusive, absolute column position)
+   * @param end
+   *          position to end at (inclusive, absolute column position)
+   * @return
+   */
+  public Iterator<int[]> getBoundedIterator(int start, int end)
+  {
+    try
+    {
+      LOCK.readLock().lock();
+      return new BoundedHiddenColsIterator(start, end, hiddenColumns);
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
+  }
+
+  /**
+   * Return a bounded iterator over the *visible* start positions of hidden
+   * regions
+   * 
+   * @param start
+   *          position to start from (inclusive, visible column position)
+   * @param end
+   *          position to end at (inclusive, visible column position)
+   */
+  public Iterator<Integer> getBoundedStartIterator(int start, int end)
+  {
+    try
+    {
+      LOCK.readLock().lock();
+      return new BoundedStartRegionIterator(start, end, hiddenColumns);
+    } finally
+    {
+      LOCK.readLock().unlock();
+    }
+  }
+
+  /**
+   * Return an iterator over visible *columns* (not regions) between the given
+   * start and end boundaries
+   * 
+   * @param start
+   *          first column (inclusive)
+   * @param end
+   *          last column (inclusive)
+   */
+  public Iterator<Integer> getVisibleColsIterator(int start, int end)
+  {
+    try
+    {
+      LOCK.readLock().lock();
+      return new VisibleColsIterator(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)
+   * @param end
+   *          (last column - not inclusive)
+   */
+  public Iterator<int[]> 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)
+   * @param end
+   *          (last column - inclusive)
+   * @param useVisibleCoords
+   *          if true, start and end are visible column positions, not absolute
+   *          positions
+   */
+  public Iterator<int[]> getVisibleBlocksIterator(int start, int end,
+          boolean useVisibleCoords)
+  {
+    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<int[]>
+  {
+    private List<int[]> vcontigs = new ArrayList<>();
+
+    private int currentPosition = 0;
+
+    private boolean endsAtHidden = false;
+
+    /**
+     * Constructor for iterator over visible regions in a range.
+     * 
+     * @param start
+     *          start position in terms of visible column position
+     * @param end
+     *          end position in terms of visible column position
+     * @param usecopy
+     *          whether to use a local copy of hidden columns
+     */
+    VisibleBlocksVisBoundsIterator(int start, int end, boolean usecopy)
+    {
+      /* actually this implementation always uses a local copy but this may change in future */
+      try
+      {
+        if (usecopy)
+        {
+          LOCK.readLock().lock();
+        }
+
+        if (hiddenColumns != null && hiddenColumns.size() > 0)
+        {
+          int blockStart = start;
+          int blockEnd = end;
+          int hiddenSoFar = 0;
+          int visSoFar = 0;
+
+          // iterate until a region begins within (start,end]
+          int i = 0;
+          while ((i < hiddenColumns.size())
+                  && (hiddenColumns.get(i)[0] <= blockStart + hiddenSoFar))
+          {
+            hiddenSoFar += hiddenColumns.get(i)[1] - hiddenColumns.get(i)[0]
+                    + 1;
+            i++;
+          }
+
+          blockStart += hiddenSoFar; // convert start to absolute position
+          blockEnd += hiddenSoFar; // convert end to absolute position
+
+          // iterate from start to end, adding each visible region. Positions
+          // are
+          // absolute, and all hidden regions which overlap [start,end] are
+          // used.
+          while (i < hiddenColumns.size()
+                  && (hiddenColumns.get(i)[0] <= blockEnd))
+          {
+            int[] region = hiddenColumns.get(i);
+
+            // end position of this visible region is either just before the
+            // start of the next hidden region, or the absolute position of
+            // 'end', whichever is lowest
+            blockEnd = Math.min(blockEnd, region[0] - 1);
+
+            vcontigs.add(new int[] { blockStart, blockEnd });
+
+            visSoFar += blockEnd - blockStart + 1;
+
+            // next visible region starts after this hidden region
+            blockStart = region[1] + 1;
+
+            hiddenSoFar += region[1] - region[0] + 1;
+
+            // reset blockEnd to absolute position of 'end', assuming we've now
+            // passed all hidden regions before end
+            blockEnd = end + hiddenSoFar;
+
+            i++;
+          }
+          if (visSoFar < end - start)
+          {
+            // the number of visible columns we've accounted for is less than
+            // the number specified by end-start; work out the end position of
+            // the last visible region
+            blockEnd = blockStart + end - start - visSoFar;
+            vcontigs.add(new int[] { blockStart, blockEnd });
+
+            // if the last visible region ends at the next hidden region, set
+            // endsAtHidden=true
+            if (i < hiddenColumns.size()
+                    && hiddenColumns.get(i)[0] - 1 == blockEnd)
+            {
+              endsAtHidden = true;
+            }
+          }
+        }
+        else
+        {
+          // there are no hidden columns, return a single visible contig
+          vcontigs.add(new int[] { start, end });
+          endsAtHidden = false;
+        }
+      } finally
+      {
+        if (usecopy)
+        {
+          LOCK.readLock().unlock();
+        }
+      }
+    }
+
+    @Override
+    public boolean hasNext()
+    {
+      return (currentPosition < vcontigs.size());
+    }
+
+    @Override
+    public int[] next()
+    {
+      int[] result = vcontigs.get(currentPosition);
+      currentPosition++;
+      return result;
+    }
+
+    public boolean endsAtHidden()
+    {
+      return endsAtHidden;
+    }
+  }
 }
diff --git a/src/jalview/datamodel/HiddenColumnsCursor.java b/src/jalview/datamodel/HiddenColumnsCursor.java
new file mode 100644 (file)
index 0000000..7fa96c1
--- /dev/null
@@ -0,0 +1,186 @@
+package jalview.datamodel;
+
+import java.util.List;
+
+public class HiddenColumnsCursor
+{
+  // absolute position of first hidden column
+  private int firstColumn;
+
+  // absolute position of last hidden column
+  private int lastColumn;
+
+  // index of last visited region
+  private int regionIndex;
+
+  // number of hidden columns before last visited region
+  private int hiddenSoFar;
+
+  private List<int[]> hiddenColumns;
+
+  protected HiddenColumnsCursor()
+  {
+
+  }
+
+  /**
+   * Set the cursor to a position
+   * 
+   * @param first
+   *          absolute position of first hidden column
+   * @param last
+   *          absolute position of last hidden column
+   * @param index
+   *          index of last visited region
+   * @param hiddenCount
+   *          number of hidden columns before last visited region
+   */
+  protected void resetCursor(List<int[]> hiddenCols)
+  {
+    synchronized (this)
+    {
+      if ((hiddenCols != null) && (!hiddenCols.isEmpty()))
+      {
+        hiddenColumns = hiddenCols;
+        firstColumn = hiddenColumns.get(0)[0];
+        lastColumn = hiddenColumns.get(hiddenColumns.size() - 1)[1];
+        regionIndex = 0;
+        hiddenSoFar = 0;
+      }
+    }
+  }
+
+  protected void updateCursor(int index, int hiddenCount)
+  {
+    synchronized (this)
+    {
+      regionIndex = index;
+      hiddenSoFar = hiddenCount;
+    }
+  }
+
+  private synchronized int getIndex()
+  {
+    return regionIndex;
+  }
+
+  private synchronized int getHiddenSoFar()
+  {
+    return hiddenSoFar;
+  }
+
+
+  /**
+   * Get the index of the region that column is within (if column is hidden) or
+   * which is to the right of column (if column is visible). If no hidden
+   * columns are to the right, will return size of hiddenColumns. If hidden
+   * columns is empty returns -1.
+   * 
+   * @param column
+   *          absolute position of a column in the alignment
+   * @return region index
+   */
+  protected int findRegionForColumn(int column)
+  {
+    if (hiddenColumns == null)
+    {
+      return -1;
+    }
+
+    int index = regionIndex;
+    int hiddenCount = hiddenSoFar;
+
+    if (index == hiddenColumns.size())
+    {
+      // went past the end of hiddenColumns collection last time
+      index--;
+      int[] region = hiddenColumns.get(index);
+      hiddenCount -= region[1] - region[0] + 1;
+    }
+
+    if ((hiddenColumns.get(index)[0] <= column)
+            && (hiddenColumns.get(index)[1] >= column))
+    {
+      // we hit the jackpot
+      // don't need to move index
+    }
+    else if (column < firstColumn)
+    {
+      index = 0;
+      hiddenCount = 0;
+    }
+    /*else if (column > lastColumn)
+    {
+      index = hiddenColumns.size();
+      // TODO resolve here - need full hidden count
+    }*/
+    else if (column > hiddenColumns.get(index)[1]) // includes if column >
+                                                   // lastColumn
+    {
+      // iterate from where we are now, if we're lucky we'll be close by
+      // (but still better than iterating from 0)
+      while ((index < hiddenColumns.size())
+              && (hiddenColumns.get(index)[0] <= column))
+      {
+        int[] region = hiddenColumns.get(index);
+        hiddenCount += region[1] - region[0] + 1;
+        index++;
+      }
+
+    }
+    else // (column < hiddenColumns.get(regionIndex)[0])
+    {
+      while ((index > 0) && (hiddenColumns.get(index)[1] > column))
+      {
+        index--;
+        int[] region = hiddenColumns.get(index);
+        hiddenCount -= region[1] - region[0] + 1;
+      }
+    }
+    updateCursor(index, hiddenCount);
+    return index;
+  }
+
+  protected int getHiddenOffset(int column)
+  {
+    if (hiddenColumns == null)
+    {
+      return -1;
+    }
+
+    int index = getIndex();
+    int hiddenCount = getHiddenSoFar();
+
+    if (column < firstColumn)
+    {
+      index = 0;
+      hiddenCount = 0;
+    }
+    else if ((index < hiddenColumns.size())
+            && (hiddenColumns.get(index)[0] <= column + hiddenCount))
+    {
+      // iterate from where we are now, if we're lucky we'll be close by
+      // (but still better than iterating from 0)
+      while ((index < hiddenColumns.size())
+              && (hiddenColumns.get(index)[0] <= column + hiddenCount))
+      {
+        int[] region = hiddenColumns.get(index);
+        hiddenCount += region[1] - region[0] + 1;
+        index++;
+      }
+    }
+    else if (index < hiddenColumns.size())
+    {
+      while ((index > 0)
+              && (hiddenColumns.get(index - 1)[1] >= column + hiddenCount))
+      {
+        index--;
+        int[] region = hiddenColumns.get(index);
+        hiddenCount -= region[1] - region[0] + 1;
+      }
+
+    }
+    updateCursor(index, hiddenCount);
+    return hiddenCount;
+  }
+}
diff --git a/src/jalview/datamodel/RegionsIterator.java b/src/jalview/datamodel/RegionsIterator.java
new file mode 100644 (file)
index 0000000..d1251f8
--- /dev/null
@@ -0,0 +1,98 @@
+package jalview.datamodel;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A local iterator which iterates over hidden column regions in a range.
+ * Intended for use ONLY within the HiddenColumns class, because it works
+ * directly with the hiddenColumns collection without locking (callers should
+ * lock hiddenColumns).
+ */
+public class RegionsIterator implements Iterator<int[]>
+{
+  // start position to iterate from
+  private int start;
+
+  // end position to iterate to
+  private int end;
+
+  // current index in hiddenColumns
+  private int currentPosition = 0;
+
+  // current column in hiddenColumns
+  private int[] nextRegion = null;
+
+  private int[] currentRegion = null;
+
+  private int removedIndex = -1;
+
+  private final List<int[]> hiddenColumns;
+
+  // Constructor with bounds
+  RegionsIterator(int lowerBound, int upperBound, List<int[]> hiddenCols,
+          HiddenColumnsCursor cursor)
+  {
+    start = lowerBound;
+    end = upperBound;
+    hiddenColumns = hiddenCols;
+
+    if (hiddenColumns != null)
+    {
+      currentPosition = cursor.findRegionForColumn(start);
+
+      // iterate until a region overlaps with [start,end]
+      /*      currentPosition = 0;
+      while ((currentPosition < hiddenColumns.size())
+              && (hiddenColumns.get(currentPosition)[1] < start))
+      {
+        currentPosition++;
+      }*/
+      if (currentPosition < hiddenColumns.size())
+      {
+        nextRegion = hiddenColumns.get(currentPosition);
+      }
+    }
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return (hiddenColumns != null) && (nextRegion != null)
+            && (nextRegion[0] <= end);
+  }
+
+  @Override
+  public int[] next()
+  {
+    currentRegion = nextRegion;
+    currentPosition++;
+    if (currentPosition < hiddenColumns.size())
+    {
+      nextRegion = hiddenColumns.get(currentPosition);
+    }
+    else
+    {
+      nextRegion = null;
+    }
+    return currentRegion;
+  }
+
+  @Override
+  public void remove()
+  {
+    if ((currentRegion != null) && (removedIndex != currentPosition))
+    {
+      currentPosition--;
+      hiddenColumns.subList(currentPosition, currentPosition + 1).clear();
+      removedIndex = currentPosition;
+    }
+    else
+    {
+      // already removed element last returned by next()
+      // or next() has not yet been called
+      throw new IllegalStateException();
+    }
+  }
+
+}
diff --git a/src/jalview/datamodel/ReverseRegionsIterator.java b/src/jalview/datamodel/ReverseRegionsIterator.java
new file mode 100644 (file)
index 0000000..b511ab5
--- /dev/null
@@ -0,0 +1,75 @@
+package jalview.datamodel;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A local iterator which reverse iterates over hidden column regions in a
+ * range. Intended for use ONLY within the HiddenColumns class, because it works
+ * directly with the hiddenColumns collection without locking (callers should
+ * lock hiddenColumns).
+ */
+public class ReverseRegionsIterator implements Iterator<int[]>
+{
+  // start position to iterate to
+  private int start;
+
+  // end position to iterate from
+  private int end;
+
+  // current index in hiddenColumns
+  private int currentPosition = 0;
+
+  // current column in hiddenColumns
+  private int[] nextRegion = null;
+
+  private final List<int[]> hiddenColumns;
+
+  // Constructor with bounds
+  ReverseRegionsIterator(int lowerBound, int upperBound,
+          List<int[]> hiddenCols)
+  {
+    hiddenColumns = hiddenCols;
+    start = lowerBound;
+    end = upperBound;
+
+    if (hiddenColumns != null)
+    {
+      // iterate until a region overlaps with [start,end]
+      currentPosition = hiddenColumns.size() - 1;
+      while (currentPosition >= 0
+              && hiddenColumns.get(currentPosition)[1] > end)
+      {
+        currentPosition--;
+      }
+      if (currentPosition >= 0)
+      {
+        nextRegion = hiddenColumns.get(currentPosition);
+      }
+    }
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return (hiddenColumns != null) && (nextRegion != null)
+            && (nextRegion[1] >= start);
+  }
+
+  @Override
+  public int[] next()
+  {
+    int[] region = nextRegion;
+    currentPosition--;
+    if (currentPosition >= 0)
+    {
+      nextRegion = hiddenColumns.get(currentPosition);
+    }
+    else
+    {
+      nextRegion = null;
+    }
+    return region;
+  }
+
+}
index 96b0757..933c89d 100755 (executable)
@@ -413,7 +413,7 @@ public class Sequence extends ASequence implements SequenceI
   {
     if (pdbIds == null)
     {
-      pdbIds = new Vector<PDBEntry>();
+      pdbIds = new Vector<>();
       pdbIds.add(entry);
       return true;
     }
@@ -1076,6 +1076,27 @@ public class Sequence extends ASequence implements SequenceI
     return map;
   }
 
+  /**
+   * Build a bitset corresponding to sequence gaps
+   * 
+   * @return a BitSet where set values correspond to gaps in the sequence
+   */
+  @Override
+  public BitSet gapBitset()
+  {
+    BitSet gaps = new BitSet(sequence.length);
+    int j = 0;
+    while (j < sequence.length)
+    {
+      if (jalview.util.Comparison.isGap(sequence[j]))
+      {
+        gaps.set(j);
+      }
+      j++;
+    }
+    return gaps;
+  }
+
   @Override
   public int[] findPositionMap()
   {
@@ -1099,7 +1120,7 @@ public class Sequence extends ASequence implements SequenceI
   @Override
   public List<int[]> getInsertions()
   {
-    ArrayList<int[]> map = new ArrayList<int[]>();
+    ArrayList<int[]> map = new ArrayList<>();
     int lastj = -1, j = 0;
     int pos = start;
     int seqlen = sequence.length;
@@ -1397,7 +1418,7 @@ public class Sequence extends ASequence implements SequenceI
   {
     if (this.annotation == null)
     {
-      this.annotation = new Vector<AlignmentAnnotation>();
+      this.annotation = new Vector<>();
     }
     if (!this.annotation.contains(annotation))
     {
@@ -1564,7 +1585,7 @@ public class Sequence extends ASequence implements SequenceI
       return null;
     }
 
-    Vector<AlignmentAnnotation> subset = new Vector<AlignmentAnnotation>();
+    Vector<AlignmentAnnotation> subset = new Vector<>();
     Enumeration<AlignmentAnnotation> e = annotation.elements();
     while (e.hasMoreElements())
     {
@@ -1722,7 +1743,7 @@ public class Sequence extends ASequence implements SequenceI
   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
           String label)
   {
-    List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
+    List<AlignmentAnnotation> result = new ArrayList<>();
     if (this.annotation != null)
     {
       for (AlignmentAnnotation ann : annotation)
@@ -1778,7 +1799,7 @@ public class Sequence extends ASequence implements SequenceI
     }
     synchronized (dbrefs)
     {
-      List<DBRefEntry> primaries = new ArrayList<DBRefEntry>();
+      List<DBRefEntry> primaries = new ArrayList<>();
       DBRefEntry[] tmp = new DBRefEntry[1];
       for (DBRefEntry ref : dbrefs)
       {
index 6e6d1aa..c1687fe 100755 (executable)
@@ -222,6 +222,13 @@ public interface SequenceI extends ASequenceI
   public int[] gapMap();
 
   /**
+   * Build a bitset corresponding to sequence gaps
+   * 
+   * @return a BitSet where set values correspond to gaps in the sequence
+   */
+  public BitSet gapBitset();
+
+  /**
    * Returns an int array where indices correspond to each position in sequence
    * char array and the element value gives the result of findPosition for that
    * index in the sequence.
index e9437a7..c3d0ab4 100644 (file)
@@ -42,7 +42,7 @@ public class VisibleColsCollection implements AlignmentColsCollectionI
   @Override
   public Iterator<Integer> iterator()
   {
-    return new VisibleColsIterator(start, end, hidden);
+    return hidden.getVisibleColsIterator(start, end);
   }
 
   @Override
index 9de468d..2fa27ed 100644 (file)
@@ -1,31 +1,13 @@
-/*
- * 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 <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
 package jalview.datamodel;
 
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 
 /**
- * An iterator which iterates over all visible columns in an alignment
+ * Iterator over the visible *columns* (not regions) as determined by the set of
+ * hidden columns. Uses a local copy of hidden columns.
  * 
  * @author kmourao
  *
@@ -38,49 +20,53 @@ public class VisibleColsIterator implements Iterator<Integer>
 
   private int next;
 
-  private List<int[]> hidden;
+  private List<int[]> localHidden = new ArrayList<>();
 
-  private int lasthiddenregion;
+  private int nexthiddenregion;
 
-  public VisibleColsIterator(int firstcol, int lastcol,
-          HiddenColumns hiddenCols)
+  VisibleColsIterator(int firstcol, int lastcol, List<int[]> hiddenColumns)
   {
     last = lastcol;
     current = firstcol;
     next = firstcol;
-    hidden = hiddenCols.getHiddenColumnsCopy();
-    lasthiddenregion = -1;
+    nexthiddenregion = 0;
 
-    if (hidden != null)
+    if (hiddenColumns != null)
     {
       int i = 0;
-      for (i = 0; i < hidden.size(); ++i)
+      for (i = 0; i < hiddenColumns.size()
+              && (current <= hiddenColumns.get(i)[0]); ++i)
       {
-        if (current >= hidden.get(i)[0] && current <= hidden.get(i)[1])
+        if (current >= hiddenColumns.get(i)[0]
+                && current <= hiddenColumns.get(i)[1])
         {
           // current is hidden, move to right
-          current = hidden.get(i)[1] + 1;
+          current = hiddenColumns.get(i)[1] + 1;
           next = current;
-        }
-        if (current < hidden.get(i)[0])
-        {
-          break;
+          nexthiddenregion = i + 1;
         }
       }
-      lasthiddenregion = i - 1;
 
-      for (i = hidden.size() - 1; i >= 0; --i)
+      for (i = hiddenColumns.size() - 1; i >= 0
+              && (last >= hiddenColumns.get(i)[1]); --i)
       {
-        if (last >= hidden.get(i)[0] && last <= hidden.get(i)[1])
+        if (last >= hiddenColumns.get(i)[0]
+                && last <= hiddenColumns.get(i)[1])
         {
           // last is hidden, move to left
-          last = hidden.get(i)[0] - 1;
-        }
-        if (last > hidden.get(i)[1])
-        {
-          break;
+          last = hiddenColumns.get(i)[0] - 1;
         }
       }
+
+      // make a local copy of the bit we need
+      i = nexthiddenregion;
+      while (i < hiddenColumns.size() && hiddenColumns.get(i)[0] <= last)
+      {
+        int[] region = new int[] { hiddenColumns.get(i)[0],
+            hiddenColumns.get(i)[1] };
+        localHidden.add(region);
+        i++;
+      }
     }
   }
 
@@ -98,20 +84,20 @@ public class VisibleColsIterator implements Iterator<Integer>
       throw new NoSuchElementException();
     }
     current = next;
-    if ((hidden != null) && (lasthiddenregion + 1 < hidden.size()))
+    if ((localHidden != null) && (nexthiddenregion < localHidden.size()))
     {
       // still some more hidden regions
-      if (next + 1 < hidden.get(lasthiddenregion + 1)[0])
+      if (next + 1 < localHidden.get(nexthiddenregion)[0])
       {
         // next+1 is still before the next hidden region
         next++;
       }
-      else if ((next + 1 >= hidden.get(lasthiddenregion + 1)[0])
-              && (next + 1 <= hidden.get(lasthiddenregion + 1)[1]))
+      else if ((next + 1 >= localHidden.get(nexthiddenregion)[0])
+              && (next + 1 <= localHidden.get(nexthiddenregion)[1]))
       {
         // next + 1 is in the next hidden region
-        next = hidden.get(lasthiddenregion + 1)[1] + 1;
-        lasthiddenregion++;
+        next = localHidden.get(nexthiddenregion)[1] + 1;
+        nexthiddenregion++;
       }
     }
     else
diff --git a/src/jalview/datamodel/VisibleContigsIterator.java b/src/jalview/datamodel/VisibleContigsIterator.java
new file mode 100644 (file)
index 0000000..d7594f9
--- /dev/null
@@ -0,0 +1,76 @@
+package jalview.datamodel;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An iterator which iterates over visible regions in a range.
+ */
+public class VisibleContigsIterator implements Iterator<int[]>
+{
+  private List<int[]> vcontigs = new ArrayList<>();
+
+  private int currentPosition = 0;
+
+  VisibleContigsIterator(int start, int end,
+          List<int[]> hiddenColumns)
+  {
+    if (hiddenColumns != null && hiddenColumns.size() > 0)
+    {
+      int vstart = start;
+      int hideStart;
+      int hideEnd;
+
+      for (int[] region : hiddenColumns)
+      {
+        hideStart = region[0];
+        hideEnd = region[1];
+
+        // navigate to start
+        if (hideEnd < vstart)
+        {
+          continue;
+        }
+        if (hideStart > vstart)
+        {
+          int[] contig = new int[] { vstart, hideStart - 1 };
+          vcontigs.add(contig);
+        }
+        vstart = hideEnd + 1;
+
+        // exit if we're past the end
+        if (vstart >= end)
+        {
+          break;
+        }
+      }
+
+      if (vstart < end)
+      {
+        int[] contig = new int[] { vstart, end - 1 };
+        vcontigs.add(contig);
+      }
+    }
+    else
+    {
+      int[] contig = new int[] { start, end - 1 };
+      vcontigs.add(contig);
+    }
+  }
+
+  @Override
+  public boolean hasNext()
+  {
+    return (currentPosition < vcontigs.size());
+  }
+
+  @Override
+  public int[] next()
+  {
+    int[] result = vcontigs.get(currentPosition);
+    currentPosition++;
+    return result;
+  }
+}
+
index 298688b..fa052f1 100644 (file)
@@ -1862,23 +1862,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    ArrayList<int[]> hiddenColumns = null;
+    HiddenColumns hiddenColumns = null;
     if (viewport.hasHiddenColumns())
     {
-      hiddenColumns = new ArrayList<>();
       int hiddenOffset = viewport.getSelectionGroup().getStartRes();
       int hiddenCutoff = viewport.getSelectionGroup().getEndRes();
-      ArrayList<int[]> hiddenRegions = viewport.getAlignment()
-              .getHiddenColumns().getHiddenColumnsCopy();
-      for (int[] region : hiddenRegions)
-      {
-        if (region[0] >= hiddenOffset && region[1] <= hiddenCutoff)
-        {
-          hiddenColumns
-                  .add(new int[]
-                  { region[0] - hiddenOffset, region[1] - hiddenOffset });
-        }
-      }
+
+      // create new HiddenColumns object with copy of hidden regions
+      // between startRes and endRes, offset by startRes
+      hiddenColumns = new HiddenColumns(
+              viewport.getAlignment().getHiddenColumns(), hiddenOffset,
+              hiddenCutoff, hiddenOffset);
     }
 
     Desktop.jalviewClipboard = new Object[] { seqs,
@@ -2207,11 +2201,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         if (Desktop.jalviewClipboard != null
                 && Desktop.jalviewClipboard[2] != null)
         {
-          List<int[]> hc = (List<int[]>) Desktop.jalviewClipboard[2];
-          for (int[] region : hc)
-          {
-            af.viewport.hideColumns(region[0], region[1]);
-          }
+          HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
+          af.viewport.setHiddenColumns(hc);
         }
 
         // >>>This is a fix for the moment, until a better solution is
@@ -2266,11 +2257,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       if (Desktop.jalviewClipboard != null
               && Desktop.jalviewClipboard[2] != null)
       {
-        List<int[]> hc = (List<int[]>) Desktop.jalviewClipboard[2];
-        for (int region[] : hc)
-        {
-          af.viewport.hideColumns(region[0], region[1]);
-        }
+        HiddenColumns hc = (HiddenColumns) Desktop.jalviewClipboard[2];
+        af.viewport.setHiddenColumns(hc);
       }
 
       // >>>This is a fix for the moment, until a better solution is
index 90271c8..14c4022 100644 (file)
@@ -60,6 +60,7 @@ import java.awt.FontMetrics;
 import java.awt.Rectangle;
 import java.util.ArrayList;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Vector;
 
@@ -453,10 +454,10 @@ public class AlignViewport extends AlignmentViewport
    *          area
    * @return
    */
-  public int[] getViewAsVisibleContigs(boolean selectedRegionOnly)
+  public Iterator<int[]> getViewAsVisibleContigs(boolean selectedRegionOnly)
   {
-    int[] viscontigs = null;
-    int start = 0, end = 0;
+    int start = 0;
+    int end = 0;
     if (selectedRegionOnly && selectionGroup != null)
     {
       start = selectionGroup.getStartRes();
@@ -466,8 +467,7 @@ public class AlignViewport extends AlignmentViewport
     {
       end = alignment.getWidth();
     }
-    viscontigs = alignment.getHiddenColumns().getVisibleContigs(start, end);
-    return viscontigs;
+    return (alignment.getHiddenColumns().getVisContigsIterator(start, end));
   }
 
   /**
@@ -591,10 +591,10 @@ public class AlignViewport extends AlignmentViewport
    */
   public SequenceI[][] collateForPDB(PDBEntry[] pdbEntries)
   {
-    List<SequenceI[]> seqvectors = new ArrayList<SequenceI[]>();
+    List<SequenceI[]> seqvectors = new ArrayList<>();
     for (PDBEntry pdb : pdbEntries)
     {
-      List<SequenceI> choosenSeqs = new ArrayList<SequenceI>();
+      List<SequenceI> choosenSeqs = new ArrayList<>();
       for (SequenceI sq : alignment.getSequences())
       {
         Vector<PDBEntry> pdbRefEntries = sq.getDatasetSequence()
@@ -656,7 +656,7 @@ public class AlignViewport extends AlignmentViewport
     return validCharWidth;
   }
 
-  private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<String, AutoCalcSetting>();
+  private Hashtable<String, AutoCalcSetting> calcIdParams = new Hashtable<>();
 
   public AutoCalcSetting getCalcIdSettingsFor(String calcId)
   {
index 020e027..6924b63 100644 (file)
@@ -36,7 +36,6 @@ import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.awt.event.KeyEvent;
-import java.util.ArrayList;
 
 import javax.swing.ButtonGroup;
 import javax.swing.JCheckBox;
@@ -241,16 +240,6 @@ public class AnnotationColumnChooser extends AnnotationRowFilter
       {
         HiddenColumns oldHidden = av.getAnnotationColumnSelectionState()
                 .getOldHiddenColumns();
-        if (oldHidden != null)
-        {
-          ArrayList<int[]> regions = oldHidden.getHiddenColumnsCopy();
-          for (int[] positions : regions)
-          {
-            av.hideColumns(positions[0], positions[1]);
-          }
-        }
-        // TODO not clear why we need to hide all the columns (above) if we are
-        // going to copy the hidden columns over wholesale anyway
         av.getAlignment().setHiddenColumns(oldHidden);
       }
       av.sendSelection();
index b94a615..45350fd 100755 (executable)
@@ -24,6 +24,7 @@ import jalview.analysis.AlignmentUtils;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -49,7 +50,6 @@ import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.awt.geom.AffineTransform;
 import java.awt.image.BufferedImage;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.regex.Pattern;
@@ -962,12 +962,12 @@ public class AnnotationLabels extends JPanel
     Toolkit.getDefaultToolkit().getSystemClipboard()
             .setContents(new StringSelection(output), Desktop.instance);
 
-    ArrayList<int[]> hiddenColumns = null;
+    HiddenColumns hiddenColumns = null;
 
     if (av.hasHiddenColumns())
     {
-      hiddenColumns = av.getAlignment().getHiddenColumns()
-              .getHiddenColumnsCopy();
+      hiddenColumns = new HiddenColumns(
+              av.getAlignment().getHiddenColumns());
     }
 
     Desktop.jalviewClipboard = new Object[] { seqs, ds, // what is the dataset
index 4a15024..c7ec757 100644 (file)
@@ -1389,9 +1389,10 @@ public class Jalview2XML
         }
         else
         {
-          ArrayList<int[]> hiddenRegions = hidden.getHiddenColumnsCopy();
-          for (int[] region : hiddenRegions)
+          Iterator<int[]> hiddenRegions = hidden.iterator();
+          while (hiddenRegions.hasNext())
           {
+            int[] region = hiddenRegions.next();
             HiddenColumns hc = new HiddenColumns();
             hc.setStart(region[0]);
             hc.setEnd(region[1]);
index e677769..1d41de0 100755 (executable)
@@ -42,6 +42,7 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.beans.PropertyChangeEvent;
+import java.util.Iterator;
 import java.util.List;
 
 import javax.swing.JMenuItem;
@@ -174,7 +175,7 @@ public class ScalePanel extends JPanel
       });
       pop.add(item);
 
-      if (av.getAlignment().getHiddenColumns().hasHiddenColumns())
+      if (av.getAlignment().getHiddenColumns().hasManyHiddenColumns())
       {
         item = new JMenuItem(MessageManager.getString("action.reveal_all"));
         item.addActionListener(new ActionListener()
@@ -487,23 +488,18 @@ public class ScalePanel extends JPanel
 
       if (av.getShowHiddenMarkers())
       {
-        List<Integer> positions = hidden.findHiddenRegionPositions();
-        for (int pos : positions)
+        Iterator<Integer> it = hidden.getBoundedStartIterator(startx,
+                startx + widthx + 1);
+        while (it.hasNext())
         {
-          res = pos - startx;
-
-          if (res < 0 || res > widthx)
-          {
-            continue;
-          }
+          res = it.next() - startx;
 
           gg.fillPolygon(
                   new int[]
-                  { -1 + res * avCharWidth - avCharHeight / 4,
-                      -1 + res * avCharWidth + avCharHeight / 4,
-                      -1 + res * avCharWidth },
-                  new int[]
-                  { y, y, y + 2 * yOf }, 3);
+          { -1 + res * avCharWidth - avCharHeight / 4,
+              -1 + res * avCharWidth + avCharHeight / 4,
+              -1 + res * avCharWidth }, new int[]
+          { y, y, y + 2 * yOf }, 3);
         }
       }
     }
index 2a9c704..6c4391c 100755 (executable)
@@ -22,6 +22,7 @@ package jalview.gui;
 
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.HiddenColumns.VisibleBlocksVisBoundsIterator;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -42,6 +43,7 @@ import java.awt.RenderingHints;
 import java.awt.Shape;
 import java.awt.image.BufferedImage;
 import java.beans.PropertyChangeEvent;
+import java.util.Iterator;
 import java.util.List;
 
 import javax.swing.JComponent;
@@ -881,11 +883,14 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     int charWidth = av.getCharWidth();
 
     g.setColor(Color.blue);
+    int res;
     HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-    List<Integer> positions = hidden.findHiddenRegionPositions();
-    for (int pos : positions)
+
+    Iterator<Integer> it = hidden.getBoundedStartIterator(startColumn,
+            endColumn);
+    while (it.hasNext())
     {
-      int res = pos - startColumn;
+      res = it.next() - startColumn;
 
       if (res < 0 || res > endColumn - startColumn + 1)
       {
@@ -1012,29 +1017,23 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     else
     {
       int screenY = 0;
-      final int screenYMax = endRes - startRes;
-      int blockStart = startRes;
-      int blockEnd = endRes;
+      int blockStart;
+      int blockEnd;
 
-      for (int[] region : av.getAlignment().getHiddenColumns()
-              .getHiddenColumnsCopy())
-      {
-        int hideStart = region[0];
-        int hideEnd = region[1];
+      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+      VisibleBlocksVisBoundsIterator regions = (VisibleBlocksVisBoundsIterator) hidden
+              .getVisibleBlocksIterator(startRes, endRes, true);
 
-        if (hideStart <= blockStart)
-        {
-          blockStart += (hideEnd - hideStart) + 1;
-          continue;
-        }
+      while (regions.hasNext())
+      {
+        int[] region = regions.next();
+        blockEnd = region[1];
+        blockStart = region[0];
 
         /*
          * draw up to just before the next hidden region, or the end of
          * the visible region, whichever comes first
          */
-        blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
-                - screenY);
-
         g1.translate(screenY * charWidth, 0);
 
         draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
@@ -1043,7 +1042,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
          * draw the downline of the hidden column marker (ScalePanel draws the
          * triangle on top) if we reached it
          */
-        if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
+        if (av.getShowHiddenMarkers()
+                && (regions.hasNext() || regions.endsAtHidden()))
         {
           g1.setColor(Color.blue);
 
@@ -1054,23 +1054,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
         g1.translate(-screenY * charWidth, 0);
         screenY += blockEnd - blockStart + 1;
-        blockStart = hideEnd + 1;
-
-        if (screenY > screenYMax)
-        {
-          // already rendered last block
-          return;
-        }
-      }
-
-      if (screenY <= screenYMax)
-      {
-        // remaining visible region to render
-        blockEnd = blockStart + screenYMax - screenY;
-        g1.translate(screenY * charWidth, 0);
-        draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
-
-        g1.translate(-screenY * charWidth, 0);
       }
     }
 
@@ -1287,22 +1270,17 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     {
       // package into blocks of visible columns
       int screenY = 0;
-      int blockStart = startRes;
-      int blockEnd = endRes;
+      int blockStart;
+      int blockEnd;
 
-      for (int[] region : av.getAlignment().getHiddenColumns()
-              .getHiddenColumnsCopy())
+      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
+      VisibleBlocksVisBoundsIterator regions = (VisibleBlocksVisBoundsIterator) hidden
+              .getVisibleBlocksIterator(startRes, endRes, true);
+      while (regions.hasNext())
       {
-        int hideStart = region[0];
-        int hideEnd = region[1];
-
-        if (hideStart <= blockStart)
-        {
-          blockStart += (hideEnd - hideStart) + 1;
-          continue;
-        }
-
-        blockEnd = hideStart - 1;
+        int[] region = regions.next();
+        blockEnd = region[1];
+        blockStart = region[0];
 
         g.translate(screenY * charWidth, 0);
         drawPartialGroupOutline(g, group,
@@ -1310,24 +1288,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
         g.translate(-screenY * charWidth, 0);
         screenY += blockEnd - blockStart + 1;
-        blockStart = hideEnd + 1;
-
-        if (screenY > (endRes - startRes))
-        {
-          // already rendered last block
-          break;
-        }
-      }
-
-      if (screenY <= (endRes - startRes))
-      {
-        // remaining visible region to render
-        blockEnd = blockStart + (endRes - startRes) - screenY;
-        g.translate(screenY * charWidth, 0);
-        drawPartialGroupOutline(g, group,
-                blockStart, blockEnd, startSeq, endSeq, offset);
-        
-        g.translate(-screenY * charWidth, 0);
       }
     }
   }
index d2086e0..ac72e93 100644 (file)
@@ -1074,16 +1074,16 @@ public class VamsasApplication implements SelectionSource, VamsasSource
                   }
                   else
                   {
-                    // int[] intervals = colsel.getVisibleContigs(
-                    // seqsel.getStartRes(), seqsel.getEndRes() + 1);
-                    int[] intervals = hidden.getVisibleContigs(
-                            seqsel.getStartRes(), seqsel.getEndRes() + 1);
-                    for (int iv = 0; iv < intervals.length; iv += 2)
+                    Iterator<int[]> intervals = hidden
+                            .getVisContigsIterator(seqsel.getStartRes(),
+                                    seqsel.getEndRes() + 1);
+                    while (intervals.hasNext())
                     {
+                      int[] region = intervals.next();
                       Seg s = new Seg();
-                      s.setStart(intervals[iv] + 1); // vamsas indices begin at
-                      // 1, not zero.
-                      s.setEnd(intervals[iv + 1] + 1);
+                      s.setStart(region[0] + 1); // vamsas indices begin at 1,
+                                                 // not zero.
+                      s.setEnd(region[1] + 1);
                       s.setInclusive(true);
                       range.addSeg(s);
                     }
index d92608c..f3155c7 100644 (file)
@@ -60,27 +60,19 @@ public class ScaleRenderer
       column = col;
       text = txt;
     }
-
-    /**
-     * String representation for inspection when debugging only
-     */
-    @Override
-    public String toString()
-    {
-      return String.format("%s:%d:%s", major ? "major" : "minor", column,
-              text);
-    }
   }
 
   /**
-   * Calculates position markers on the alignment ruler
+   * calculate positions markers on the alignment ruler
    * 
    * @param av
    * @param startx
-   *          left-most column in visible view (0..)
+   *          left-most column in visible view
    * @param endx
-   *          - right-most column in visible view (0..)
-   * @return
+   *          - right-most column in visible view
+   * @return List of ScaleMark holding boolean: true/false for major/minor mark,
+   *         marker position in alignment column coords, a String to be rendered
+   *         at the position (or null)
    */
   public List<ScaleMark> calculateMarks(AlignViewportI av, int startx,
           int endx)
@@ -88,17 +80,23 @@ public class ScaleRenderer
     int scalestartx = (startx / 10) * 10;
 
     SequenceI refSeq = av.getAlignment().getSeqrep();
-    int refSp = 0, refStartI = 0, refEndI = -1;
+    int refSp = 0;
+    int refStartI = 0;
+    int refEndI = -1;
     if (refSeq != null)
     {
-      // find bounds and set origin appopriately
+      // find bounds and set origin appropriately
       // locate first visible position for this sequence
-      int[] refbounds = av.getAlignment().getHiddenColumns()
-              .locateVisibleBoundsOfSequence(refSeq);
+      refSp = av.getAlignment().getHiddenColumns()
+              .locateVisibleStartOfSequence(refSeq);
+
+      refStartI = refSeq.findIndex(refSeq.getStart()) - 1;
+
+      int seqlength = refSeq.getLength();
+      // get sequence position past the end of the sequence
+      int pastEndPos = refSeq.findPosition(seqlength + 1);
+      refEndI = refSeq.findIndex(pastEndPos - 1) - 1;
 
-      refSp = refbounds[0];
-      refStartI = refbounds[4];
-      refEndI = refbounds[5];
       scalestartx = refSp + ((scalestartx - refSp) / 10) * 10;
     }
 
@@ -106,41 +104,42 @@ public class ScaleRenderer
     {
       scalestartx += 5;
     }
-    List<ScaleMark> marks = new ArrayList<ScaleMark>();
+    List<ScaleMark> marks = new ArrayList<>();
+    String string;
+    int refN, iadj;
     // todo: add a 'reference origin column' to set column number relative to
-    for (int i = scalestartx; i <= endx; i += 5)
+    for (int i = scalestartx; i < endx; i += 5)
     {
       if (((i - refSp) % 10) == 0)
       {
-        String text;
         if (refSeq == null)
         {
-          int iadj = av.getAlignment().getHiddenColumns()
+          iadj = av.getAlignment().getHiddenColumns()
                   .adjustForHiddenColumns(i - 1) + 1;
-          text = String.valueOf(iadj);
+          string = String.valueOf(iadj);
         }
         else
         {
-          int iadj = av.getAlignment().getHiddenColumns()
+          iadj = av.getAlignment().getHiddenColumns()
                   .adjustForHiddenColumns(i - 1);
-          int refN = refSeq.findPosition(iadj);
+          refN = refSeq.findPosition(iadj);
           // TODO show bounds if position is a gap
           // - ie L--R -> "1L|2R" for
           // marker
           if (iadj < refStartI)
           {
-            text = String.valueOf(iadj - refStartI);
+            string = String.valueOf(iadj - refStartI);
           }
           else if (iadj > refEndI)
           {
-            text = "+" + String.valueOf(iadj - refEndI);
+            string = "+" + String.valueOf(iadj - refEndI);
           }
           else
           {
-            text = String.valueOf(refN) + refSeq.getCharAt(iadj);
+            string = String.valueOf(refN) + refSeq.getCharAt(iadj);
           }
         }
-        marks.add(new ScaleMark(true, i - startx - 1, text));
+        marks.add(new ScaleMark(true, i - startx - 1, string));
       }
       else
       {
index 9c5c109..5a26ed6 100644 (file)
@@ -542,9 +542,11 @@ public final class MappingUtils
               toSequences, fromGapChar);
     }
 
-    for (int[] hidden : hiddencols.getHiddenColumnsCopy())
+    Iterator<int[]> regions = hiddencols.iterator();
+    while (regions.hasNext())
     {
-      mapHiddenColumns(hidden, codonFrames, newHidden, fromSequences,
+      mapHiddenColumns(regions.next(), codonFrames, newHidden,
+              fromSequences,
               toSequences, fromGapChar);
     }
     return; // mappedColumns;
index d2fa99a..1ed846a 100644 (file)
@@ -38,6 +38,7 @@ import jalview.io.FileFormat;
 import jalview.io.FormatAdapter;
 
 import java.io.IOException;
+import java.util.Iterator;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -135,7 +136,8 @@ public class DnaTest
             FileFormat.Fasta);
     HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(alf, cs);
-    Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 });
+    Iterator<int[]> contigs = cs.getVisContigsIterator(0, alf.getWidth());
+    Dna dna = new Dna(av, contigs);
     AlignmentI translated = dna.translateCdna();
     assertNotNull("Couldn't do a full width translation of test data.",
             translated);
@@ -163,7 +165,8 @@ public class DnaTest
         cs.hideColumns(0, ipos - 1);
       }
       cs.hideColumns(ipos + vwidth, alf.getWidth());
-      int[] vcontigs = cs.getVisibleContigs(0, alf.getWidth());
+      Iterator<int[]> vcontigs = cs.getVisContigsIterator(0,
+              alf.getWidth());
       AlignViewportI av = new AlignViewport(alf, cs);
       Dna dna = new Dna(av, vcontigs);
       AlignmentI transAlf = dna.translateCdna();
@@ -190,7 +193,8 @@ public class DnaTest
             DataSourceType.PASTE, FileFormat.Fasta);
     HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(alf, cs);
-    Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 });
+    Iterator<int[]> contigs = cs.getVisContigsIterator(0, alf.getWidth());
+    Dna dna = new Dna(av, contigs);
     AlignmentI translated = dna.translateCdna();
     String aa = translated.getSequenceAt(0).getSequenceAsString();
     assertEquals(
@@ -213,7 +217,8 @@ public class DnaTest
     cs.hideColumns(24, 35); // hide codons 9-12
     cs.hideColumns(177, 191); // hide codons 60-64
     AlignViewportI av = new AlignViewport(alf, cs);
-    Dna dna = new Dna(av, new int[] { 0, alf.getWidth() - 1 });
+    Iterator<int[]> contigs = cs.getVisContigsIterator(0, alf.getWidth());
+    Dna dna = new Dna(av, contigs);
     AlignmentI translated = dna.translateCdna();
     String aa = translated.getSequenceAt(0).getSequenceAsString();
     assertEquals("AACDDGGGGHHIIIKKLLLLLLMNNPPPPQQRRRRRRSSSSSSTTTTVVVVW", aa);
@@ -298,7 +303,8 @@ public class DnaTest
             .generate(12, 8, 97, 5, 5);
     HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(cdna, cs);
-    Dna dna = new Dna(av, new int[] { 0, cdna.getWidth() - 1 });
+    Iterator<int[]> contigs = cs.getVisContigsIterator(0, cdna.getWidth());
+    Dna dna = new Dna(av, contigs);
     AlignmentI translated = dna.translateCdna();
 
     /*
@@ -313,7 +319,8 @@ public class DnaTest
     }
     AlignmentI cdnaReordered = new Alignment(sorted);
     av = new AlignViewport(cdnaReordered, cs);
-    dna = new Dna(av, new int[] { 0, cdna.getWidth() - 1 });
+    contigs = cs.getVisContigsIterator(0, cdna.getWidth());
+    dna = new Dna(av, contigs);
     AlignmentI translated2 = dna.translateCdna();
 
     /*
@@ -544,7 +551,8 @@ public class DnaTest
 
     HiddenColumns cs = new HiddenColumns();
     AlignViewportI av = new AlignViewport(al, cs);
-    Dna testee = new Dna(av, new int[] { 0, al.getWidth() - 1 });
+    Iterator<int[]> contigs = cs.getVisContigsIterator(0, al.getWidth());
+    Dna testee = new Dna(av, contigs);
     AlignmentI reversed = testee.reverseCdna(false);
     assertEquals(1, reversed.getHeight());
     assertEquals(seqRev, reversed.getSequenceAt(0).getSequenceAsString());
index e99e952..8709961 100644 (file)
@@ -32,6 +32,7 @@ import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.ConcurrentModificationException;
+import java.util.Iterator;
 import java.util.List;
 
 import org.testng.annotations.BeforeClass;
@@ -132,9 +133,9 @@ public class ColumnSelectionTest
     // hide column 5 (and adjacent):
     cs.hideSelectedColumns(5, al.getHiddenColumns());
     // 4,5,6 now hidden:
-    List<int[]> hidden = al.getHiddenColumns().getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
+    Iterator<int[]> regions = al.getHiddenColumns().iterator();
+    assertEquals(1, al.getHiddenColumns().getNumberOfRegions());
+    assertEquals("[4, 6]", Arrays.toString(regions.next()));
     // none now selected:
     assertTrue(cs.getSelected().isEmpty());
 
@@ -145,9 +146,9 @@ public class ColumnSelectionTest
     cs.addElement(5);
     cs.addElement(6);
     cs.hideSelectedColumns(4, al.getHiddenColumns());
-    hidden = al.getHiddenColumns().getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
+    regions = al.getHiddenColumns().iterator();
+    assertEquals(1, al.getHiddenColumns().getNumberOfRegions());
+    assertEquals("[4, 6]", Arrays.toString(regions.next()));
     assertTrue(cs.getSelected().isEmpty());
 
     // repeat, hiding column (4, 5 and) 6
@@ -157,9 +158,9 @@ public class ColumnSelectionTest
     cs.addElement(5);
     cs.addElement(6);
     cs.hideSelectedColumns(6, al.getHiddenColumns());
-    hidden = al.getHiddenColumns().getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
+    regions = al.getHiddenColumns().iterator();
+    assertEquals(1, al.getHiddenColumns().getNumberOfRegions());
+    assertEquals("[4, 6]", Arrays.toString(regions.next()));
     assertTrue(cs.getSelected().isEmpty());
 
     // repeat, with _only_ adjacent columns selected
@@ -168,9 +169,9 @@ public class ColumnSelectionTest
     cs.addElement(4);
     cs.addElement(6);
     cs.hideSelectedColumns(5, al.getHiddenColumns());
-    hidden = al.getHiddenColumns().getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
+    regions = al.getHiddenColumns().iterator();
+    assertEquals(1, al.getHiddenColumns().getNumberOfRegions());
+    assertEquals("[4, 6]", Arrays.toString(regions.next()));
     assertTrue(cs.getSelected().isEmpty());
   }
 
@@ -196,12 +197,12 @@ public class ColumnSelectionTest
 
     cs.hideSelectedColumns(al);
     assertTrue(cs.getSelected().isEmpty());
-    List<int[]> hidden = cols.getHiddenColumnsCopy();
-    assertEquals(4, hidden.size());
-    assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
-    assertEquals("[7, 9]", Arrays.toString(hidden.get(1)));
-    assertEquals("[15, 18]", Arrays.toString(hidden.get(2)));
-    assertEquals("[20, 22]", Arrays.toString(hidden.get(3)));
+    Iterator<int[]> regions = cols.iterator();
+    assertEquals(4, cols.getNumberOfRegions());
+    assertEquals("[2, 4]", Arrays.toString(regions.next()));
+    assertEquals("[7, 9]", Arrays.toString(regions.next()));
+    assertEquals("[15, 18]", Arrays.toString(regions.next()));
+    assertEquals("[20, 22]", Arrays.toString(regions.next()));
   }
 
   /**
index 7c88d71..3147aeb 100644 (file)
@@ -27,10 +27,11 @@ import static org.testng.AssertJUnit.assertTrue;
 
 import jalview.analysis.AlignmentGenerator;
 import jalview.gui.JvOptionPane;
+import jalview.util.Comparison;
 
 import java.util.Arrays;
 import java.util.BitSet;
-import java.util.List;
+import java.util.Iterator;
 import java.util.Random;
 
 import org.testng.annotations.BeforeClass;
@@ -118,89 +119,63 @@ public class HiddenColumnsTest
     HiddenColumns cs3 = new HiddenColumns();
     cs3.hideColumns(0, 4);
     assertEquals(0, cs3.findColumnPosition(2));
-
   }
 
-  /**
-   * Test the method that finds the visible column position a given distance
-   * before another column
-   */
   @Test(groups = { "Functional" })
-  public void testFindColumnNToLeft()
+  public void testVisibleContigsIterator()
   {
     HiddenColumns cs = new HiddenColumns();
 
-    // test that without hidden columns, findColumnNToLeft returns
-    // position n to left of provided position
-    int pos = cs.subtractVisibleColumns(3, 10);
-    assertEquals(7, pos);
+    Iterator<int[]> visible = cs.getVisContigsIterator(3, 10);
+    int[] region = visible.next();
+    assertEquals("[3, 9]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
 
-    // 0 returns same position
-    pos = cs.subtractVisibleColumns(0, 10);
-    assertEquals(10, pos);
-
-    // overflow to left returns negative number
-    pos = cs.subtractVisibleColumns(3, 0);
-    assertEquals(-3, pos);
-
-    // test that with hidden columns to left of result column
-    // behaviour is the same as above
-    cs.hideColumns(1, 3);
-
-    // position n to left of provided position
-    pos = cs.subtractVisibleColumns(3, 10);
-    assertEquals(7, pos);
-
-    // 0 returns same position
-    pos = cs.subtractVisibleColumns(0, 10);
-    assertEquals(10, pos);
-
-    // test with one set of hidden columns between start and required position
-    cs.hideColumns(12, 15);
-    pos = cs.subtractVisibleColumns(8, 17);
-    assertEquals(5, pos);
-
-    // test with two sets of hidden columns between start and required position
-    cs.hideColumns(20, 21);
-    pos = cs.subtractVisibleColumns(8, 23);
-    assertEquals(9, pos);
-
-    // repeat last 2 tests with no hidden columns to left of required position
-    ColumnSelection colsel = new ColumnSelection();
-    cs.revealAllHiddenColumns(colsel);
-
-    // test with one set of hidden columns between start and required position
-    cs.hideColumns(12, 15);
-    pos = cs.subtractVisibleColumns(8, 17);
-    assertEquals(5, pos);
-
-    // test with two sets of hidden columns between start and required position
-    cs.hideColumns(20, 21);
-    pos = cs.subtractVisibleColumns(8, 23);
-    assertEquals(9, pos);
-
-  }
-
-  @Test(groups = { "Functional" })
-  public void testGetVisibleContigs()
-  {
-    HiddenColumns cs = new HiddenColumns();
     cs.hideColumns(3, 6);
     cs.hideColumns(8, 9);
     cs.hideColumns(12, 12);
 
-    // start position is inclusive, end position exclusive:
-    int[] visible = cs.getVisibleContigs(1, 13);
-    assertEquals("[1, 2, 7, 7, 10, 11]", Arrays.toString(visible));
-
-    visible = cs.getVisibleContigs(4, 14);
-    assertEquals("[7, 7, 10, 11, 13, 13]", Arrays.toString(visible));
-
-    visible = cs.getVisibleContigs(3, 10);
-    assertEquals("[7, 7]", Arrays.toString(visible));
-
-    visible = cs.getVisibleContigs(4, 6);
-    assertEquals("[]", Arrays.toString(visible));
+    // Test both ends visible region
+
+    // start position is inclusive, end position exclusive
+    visible = cs.getVisContigsIterator(1, 13);
+    region = visible.next();
+    assertEquals("[1, 2]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[7, 7]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[10, 11]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // Test start hidden, end visible
+    visible = cs.getVisContigsIterator(4, 14);
+    region = visible.next();
+    assertEquals("[7, 7]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[10, 11]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[13, 13]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // Test start hidden, end hidden
+    visible = cs.getVisContigsIterator(3, 10);
+    region = visible.next();
+    assertEquals("[7, 7]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // Test start visible, end hidden
+    visible = cs.getVisContigsIterator(0, 13);
+    region = visible.next();
+    assertEquals("[0, 2]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[7, 7]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[10, 11]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // Test empty result
+    visible = cs.getVisContigsIterator(4, 6);
+    assertFalse(visible.hasNext());
   }
 
   @Test(groups = { "Functional" })
@@ -232,17 +207,48 @@ public class HiddenColumnsTest
     HiddenColumns cs = new HiddenColumns();
     cs.hideColumns(10, 11);
     cs.hideColumns(5, 7);
+    Iterator<int[]> regions = cs.iterator();
     assertEquals("[5, 7]",
-            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
 
     HiddenColumns cs2 = new HiddenColumns(cs);
+    regions = cs2.iterator();
     assertTrue(cs2.hasHiddenColumns());
-    assertEquals(2, cs2.getHiddenColumnsCopy().size());
+    assertEquals(2, cs2.getNumberOfRegions());
     // hidden columns are held in column order
     assertEquals("[5, 7]",
-            Arrays.toString(cs2.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
     assertEquals("[10, 11]",
-            Arrays.toString(cs2.getHiddenColumnsCopy().get(1)));
+            Arrays.toString(regions.next()));
+  }
+
+  @Test(groups = "Functional")
+  public void testCopyConstructor2()
+  {
+    HiddenColumns cs = new HiddenColumns();
+    cs.hideColumns(10, 11);
+    cs.hideColumns(5, 7);
+
+    HiddenColumns cs2 = new HiddenColumns(cs, 3, 9, 1);
+    assertTrue(cs2.hasHiddenColumns());
+    Iterator<int[]> regions = cs2.iterator();
+
+    // only [5,7] returned, offset by 1
+    assertEquals("[4, 6]",
+            Arrays.toString(regions.next()));
+    assertEquals(3, cs2.getSize());
+
+    cs2 = new HiddenColumns(cs, 8, 15, 4);
+    regions = cs2.iterator();
+    assertTrue(cs2.hasHiddenColumns());
+
+    // only [10,11] returned, offset by 4
+    assertEquals("[6, 7]",
+            Arrays.toString(regions.next()));
+    assertEquals(2, cs2.getSize());
+
+    cs2 = new HiddenColumns(cs, 6, 10, 4);
+    assertFalse(cs2.hasHiddenColumns());
   }
 
   /**
@@ -262,54 +268,137 @@ public class HiddenColumnsTest
     assertEquals(2, seq.findIndex(seq.getStart()));
 
     // no hidden columns
-    assertEquals(
-            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
-                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+    assertEquals(seq.findIndex(seq.getStart()) - 1, cs.locateVisibleStartOfSequence(seq));
 
     // hidden column on gap after end of sequence - should not affect bounds
     colsel.hideSelectedColumns(13, al.getHiddenColumns());
-    assertEquals(
-            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
-                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+    assertEquals(seq.findIndex(seq.getStart()) - 1,cs.locateVisibleStartOfSequence(seq));
 
     cs.revealAllHiddenColumns(colsel);
     // hidden column on gap before beginning of sequence - should vis bounds by
     // one
     colsel.hideSelectedColumns(0, al.getHiddenColumns());
-    assertEquals(
-            Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 2,
-                seq.findIndex(seq.getEnd()) - 2, seq.getStart(),
-                seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+    assertEquals(seq.findIndex(seq.getStart()) - 2,cs.locateVisibleStartOfSequence(seq));
 
     cs.revealAllHiddenColumns(colsel);
     // hide columns around most of sequence - leave one residue remaining
     cs.hideColumns(1, 3);
     cs.hideColumns(6, 11);
     assertEquals("-D",
-            cs.getVisibleSequenceStrings(0, 5, new SequenceI[] { seq })[0]);
-    assertEquals(
-            Arrays.toString(new int[] { 1, 1, 3, 3,
-                seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+            cs.getVisibleSequenceStrings(0, 5, new SequenceI[]
+    { seq })[0]);
+
+    assertEquals(1, cs.locateVisibleStartOfSequence(seq));
     cs.revealAllHiddenColumns(colsel);
 
     // hide whole sequence - should just get location of hidden region
     // containing sequence
     cs.hideColumns(1, 11);
-    assertEquals(
-            Arrays.toString(new int[] { 0, 1, 0, 0,
-                seq.findIndex(seq.getStart()) - 1,
-                seq.findIndex(seq.getEnd()) - 1 }),
-            Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 15);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    SequenceI seq2 = new Sequence("RefSeq2", "-------A-SD-ASD--E---");
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(7, 17);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq2));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(3, 17);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq2));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(3, 19);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq2));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 0);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 1);
+    assertEquals(1,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 2);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(1, 1);
+    assertEquals(2,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(1, 2);
+    assertEquals(1,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(1, 3);
+    assertEquals(1,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 2);
+    cs.hideColumns(5, 6);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 2);
+    cs.hideColumns(5, 6);
+    cs.hideColumns(9, 10);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 2);
+    cs.hideColumns(7, 11);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(2, 4);
+    cs.hideColumns(7, 11);
+    assertEquals(1,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(2, 4);
+    cs.hideColumns(7, 12);
+    assertEquals(1,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(1, 11);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 12);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 4);
+    cs.hideColumns(6, 12);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 1);
+    cs.hideColumns(3, 12);
+    assertEquals(0,cs.locateVisibleStartOfSequence(seq));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(3, 14);
+    cs.hideColumns(17, 19);
+    assertEquals(3,cs.locateVisibleStartOfSequence(seq2));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(3, 7);
+    cs.hideColumns(9, 14);
+    cs.hideColumns(17, 19);
+    assertEquals(9,cs.locateVisibleStartOfSequence(seq2));
+
+    cs.revealAllHiddenColumns(colsel);
+    cs.hideColumns(0, 1);
+    cs.hideColumns(3, 4);
+    cs.hideColumns(6, 8);
+    cs.hideColumns(10, 12);
+    assertEquals(6, cs.locateVisibleStartOfSequence(seq));
 
   }
 
@@ -317,16 +406,13 @@ public class HiddenColumnsTest
   public void testLocateVisibleBoundsPathologicals()
   {
     // test some pathological cases we missed
-    AlignmentI al = new Alignment(new SequenceI[] { new Sequence(
-            "refseqGaptest", "KTDVTI----------NFI-----G----L") });
+    AlignmentI al = new Alignment(
+            new SequenceI[]
+    { new Sequence("refseqGaptest", "KTDVTI----------NFI-----G----L") });
     HiddenColumns cs = new HiddenColumns();
     cs.hideInsertionsFor(al.getSequenceAt(0));
-    assertEquals(
-            "G",
-            ""
-                    + al.getSequenceAt(0).getCharAt(
-                            cs.adjustForHiddenColumns(9)));
-
+    assertEquals("G", ""
+            + al.getSequenceAt(0).getCharAt(cs.adjustForHiddenColumns(9)));
   }
 
   @Test(groups = { "Functional" })
@@ -339,80 +425,79 @@ public class HiddenColumnsTest
     ColumnSelection colsel = new ColumnSelection();
     HiddenColumns cs = al.getHiddenColumns();
     colsel.hideSelectedColumns(5, al.getHiddenColumns());
-    List<int[]> hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[5, 5]", Arrays.toString(hidden.get(0)));
+    Iterator<int[]> regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[5, 5]", Arrays.toString(regions.next()));
 
     colsel.hideSelectedColumns(3, al.getHiddenColumns());
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(2, hidden.size());
+    regions = cs.iterator();
+    assertEquals(2, cs.getNumberOfRegions());
     // two hidden ranges, in order:
-    assertEquals(hidden.size(), cs.getHiddenColumnsCopy().size());
-    assertEquals("[3, 3]", Arrays.toString(hidden.get(0)));
-    assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
+    assertEquals("[3, 3]", Arrays.toString(regions.next()));
+    assertEquals("[5, 5]", Arrays.toString(regions.next()));
 
     // hiding column 4 expands [3, 3] to [3, 4]
     // and merges to [5, 5] to make [3, 5]
     colsel.hideSelectedColumns(4, al.getHiddenColumns());
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[3, 5]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[3, 5]", Arrays.toString(regions.next()));
 
     // clear hidden columns (note they are added to selected)
     cs.revealAllHiddenColumns(colsel);
     // it is now actually null but getter returns an empty list
-    assertTrue(cs.getHiddenColumnsCopy().isEmpty());
+    assertEquals(0, cs.getNumberOfRegions());
 
     cs.hideColumns(3, 6);
-    hidden = cs.getHiddenColumnsCopy();
-    int[] firstHiddenRange = hidden.get(0);
+    regions = cs.iterator();
+    int[] firstHiddenRange = regions.next();
     assertEquals("[3, 6]", Arrays.toString(firstHiddenRange));
 
     // adding a subrange of already hidden should do nothing
     cs.hideColumns(4, 5);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
     assertEquals("[3, 6]",
-            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
     cs.hideColumns(3, 5);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
     assertEquals("[3, 6]",
-            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
     cs.hideColumns(4, 6);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
     assertEquals("[3, 6]",
-            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
     cs.hideColumns(3, 6);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
     assertEquals("[3, 6]",
-            Arrays.toString(cs.getHiddenColumnsCopy().get(0)));
+            Arrays.toString(regions.next()));
 
     cs.revealAllHiddenColumns(colsel);
     cs.hideColumns(2, 4);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[2, 4]", Arrays.toString(regions.next()));
 
     // extend contiguous with 2 positions overlap
     cs.hideColumns(3, 5);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[2, 5]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[2, 5]", Arrays.toString(regions.next()));
 
     // extend contiguous with 1 position overlap
     cs.hideColumns(5, 6);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[2, 6]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[2, 6]", Arrays.toString(regions.next()));
 
     // extend contiguous with overlap both ends:
     cs.hideColumns(1, 7);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[1, 7]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[1, 7]", Arrays.toString(regions.next()));
   }
 
   /**
@@ -427,8 +512,10 @@ public class HiddenColumnsTest
     cs.hideColumns(5, 8);
     colsel.addElement(10);
     cs.revealHiddenColumns(5, colsel);
-    // hidden columns list now null but getter returns empty list:
-    assertTrue(cs.getHiddenColumnsCopy().isEmpty());
+
+    // hiddenColumns now empty
+    assertEquals(0, cs.getSize());
+
     // revealed columns are marked as selected (added to selection):
     assertEquals("[10, 5, 6, 7, 8]", colsel.getSelected().toString());
 
@@ -436,30 +523,34 @@ public class HiddenColumnsTest
     colsel = new ColumnSelection();
     cs = new HiddenColumns();
     cs.hideColumns(5, 8);
-    List<int[]> hidden = cs.getHiddenColumnsCopy();
+
+    int prevSize = cs.getSize();
     cs.revealHiddenColumns(6, colsel);
-    assertEquals(hidden.size(), cs.getHiddenColumnsCopy().size());
+    assertEquals(prevSize, cs.getSize());
     assertTrue(colsel.getSelected().isEmpty());
   }
 
   @Test(groups = { "Functional" })
   public void testRevealAllHiddenColumns()
   {
-    HiddenColumns cs = new HiddenColumns();
+    HiddenColumns hidden = new HiddenColumns();
     ColumnSelection colsel = new ColumnSelection();
-    cs.hideColumns(5, 8);
-    cs.hideColumns(2, 3);
+    hidden.hideColumns(5, 8);
+    hidden.hideColumns(2, 3);
     colsel.addElement(11);
     colsel.addElement(1);
-    cs.revealAllHiddenColumns(colsel);
+    hidden.revealAllHiddenColumns(colsel);
 
     /*
      * revealing hidden columns adds them (in order) to the (unordered)
      * selection list
      */
-    assertTrue(cs.getHiddenColumnsCopy().isEmpty());
-    assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", colsel.getSelected()
-            .toString());
+
+    // hiddenColumns now empty
+    assertEquals(0, hidden.getSize());
+
+    assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]",
+            colsel.getSelected().toString());
   }
 
   @Test(groups = { "Functional" })
@@ -493,15 +584,15 @@ public class HiddenColumnsTest
     HiddenColumns cs = new HiddenColumns();
     cs.hideColumns(49, 59);
     cs.hideColumns(69, 79);
-    List<int[]> hidden = cs.getHiddenColumnsCopy();
-    assertEquals(2, hidden.size());
-    assertEquals("[49, 59]", Arrays.toString(hidden.get(0)));
-    assertEquals("[69, 79]", Arrays.toString(hidden.get(1)));
+    Iterator<int[]> regions = cs.iterator();
+    assertEquals(2, cs.getNumberOfRegions());
+    assertEquals("[49, 59]", Arrays.toString(regions.next()));
+    assertEquals("[69, 79]", Arrays.toString(regions.next()));
 
     cs.hideColumns(48, 80);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[48, 80]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[48, 80]", Arrays.toString(regions.next()));
 
     /*
      * another...joining hidden ranges
@@ -512,9 +603,9 @@ public class HiddenColumnsTest
     cs.hideColumns(50, 60);
     // hiding 21-49 should merge to one range
     cs.hideColumns(21, 49);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[10, 60]", Arrays.toString(hidden.get(0)));
+    regions = cs.iterator();
+    assertEquals(1, cs.getNumberOfRegions());
+    assertEquals("[10, 60]", Arrays.toString(regions.next()));
 
     /*
      * another...left overlap, subsumption, right overlap,
@@ -528,10 +619,10 @@ public class HiddenColumnsTest
     cs.hideColumns(60, 70);
 
     cs.hideColumns(15, 45);
-    hidden = cs.getHiddenColumnsCopy();
-    assertEquals(2, hidden.size());
-    assertEquals("[10, 50]", Arrays.toString(hidden.get(0)));
-    assertEquals("[60, 70]", Arrays.toString(hidden.get(1)));
+    regions = cs.iterator();
+    assertEquals(2, cs.getNumberOfRegions());
+    assertEquals("[10, 50]", Arrays.toString(regions.next()));
+    assertEquals("[60, 70]", Arrays.toString(regions.next()));
   }
 
   @Test(groups = { "Functional" })
@@ -545,23 +636,23 @@ public class HiddenColumnsTest
     one.set(1);
     cs = new HiddenColumns();
     cs.hideMarkedBits(one);
-    assertEquals(1, cs.getHiddenColumnsCopy().size());
+    assertEquals(1, cs.getNumberOfRegions());
 
     one.set(2);
     cs = new HiddenColumns();
     cs.hideMarkedBits(one);
-    assertEquals(1, cs.getHiddenColumnsCopy().size());
+    assertEquals(1, cs.getNumberOfRegions());
 
     one.set(3);
     cs = new HiddenColumns();
     cs.hideMarkedBits(one);
-    assertEquals(1, cs.getHiddenColumnsCopy().size());
+    assertEquals(1, cs.getNumberOfRegions());
 
     // split
     one.clear(2);
     cs = new HiddenColumns();
     cs.hideMarkedBits(one);
-    assertEquals(2, cs.getHiddenColumnsCopy().size());
+    assertEquals(2, cs.getNumberOfRegions());
 
     assertEquals(0, cs.adjustForHiddenColumns(0));
     assertEquals(2, cs.adjustForHiddenColumns(1));
@@ -572,7 +663,7 @@ public class HiddenColumnsTest
     cs = new HiddenColumns();
     cs.hideMarkedBits(one);
 
-    assertEquals(1, cs.getHiddenColumnsCopy().size());
+    assertEquals(1, cs.getNumberOfRegions());
 
     assertEquals(0, cs.adjustForHiddenColumns(0));
     assertEquals(1, cs.adjustForHiddenColumns(1));
@@ -581,7 +672,7 @@ public class HiddenColumnsTest
   }
 
   @Test(groups = { "Functional" })
-  public void testGetBitset()
+  public void testMarkHiddenRegions()
   {
     BitSet toMark, fromMark;
     long seed = -3241532;
@@ -589,8 +680,9 @@ public class HiddenColumnsTest
     for (int n = 0; n < 1000; n++)
     {
       // create a random bitfield
-      toMark = BitSet.valueOf(new long[] { number.nextLong(),
-          number.nextLong(), number.nextLong() });
+      toMark = BitSet
+              .valueOf(new long[]
+      { number.nextLong(), number.nextLong(), number.nextLong() });
       toMark.set(n * number.nextInt(10), n * (25 + number.nextInt(25)));
       HiddenColumns hc = new HiddenColumns();
       hc.hideMarkedBits(toMark);
@@ -602,25 +694,6 @@ public class HiddenColumnsTest
   }
 
   @Test(groups = { "Functional" })
-  public void testFindHiddenRegionPositions()
-  {
-    HiddenColumns hc = new HiddenColumns();
-
-    List<Integer> positions = hc.findHiddenRegionPositions();
-    assertTrue(positions.isEmpty());
-
-    hc.hideColumns(3, 7);
-    hc.hideColumns(10, 10);
-    hc.hideColumns(14, 15);
-
-    positions = hc.findHiddenRegionPositions();
-    assertEquals(3, positions.size());
-    assertEquals(3, positions.get(0).intValue());
-    assertEquals(5, positions.get(1).intValue());
-    assertEquals(8, positions.get(2).intValue());
-  }
-
-  @Test(groups = { "Functional" })
   public void testRegionsToString()
   {
     HiddenColumns hc = new HiddenColumns();
@@ -637,7 +710,7 @@ public class HiddenColumnsTest
   }
 
   @Test(groups = "Functional")
-  public void getVisibleStartAndEndIndexTest()
+  public void testGetVisibleStartAndEndIndexTest()
   {
     Sequence seq = new Sequence("testSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
     AlignmentI align = new Alignment(new SequenceI[] { seq });
@@ -677,7 +750,7 @@ public class HiddenColumnsTest
     hc.hideColumns(10, 10);
     hc.hideColumns(14, 15);
 
-    result = hc.getRegionWithEdgeAtRes(3);
+    result = hc.getRegionWithEdgeAtRes(2);
     assertEquals(3, result[0]);
     assertEquals(7, result[1]);
 
@@ -687,6 +760,17 @@ public class HiddenColumnsTest
 
     result = hc.getRegionWithEdgeAtRes(6);
     assertNull(result);
+
+    result = hc.getRegionWithEdgeAtRes(0);
+    assertNull(result);
+
+    result = hc.getRegionWithEdgeAtRes(7);
+    assertEquals(14, result[0]);
+    assertEquals(15, result[1]);
+
+    result = hc.getRegionWithEdgeAtRes(8);
+    assertEquals(14, result[0]);
+    assertEquals(15, result[1]);
   }
 
   @Test(groups = "Functional")
@@ -695,7 +779,7 @@ public class HiddenColumnsTest
     // create an alignment with no gaps - this will be the profile seq and other
     // JPRED seqs
     AlignmentGenerator gen = new AlignmentGenerator(false);
-    AlignmentI al = gen.generate(20, 10, 1234, 0, 0);
+    AlignmentI al = gen.generate(25, 10, 1234, 0, 0);
 
     // get the profileseq
     SequenceI profileseq = al.getSequenceAt(0);
@@ -704,7 +788,7 @@ public class HiddenColumnsTest
     gappedseq.insertCharAt(6, al.getGapCharacter());
     gappedseq.insertCharAt(7, al.getGapCharacter());
     gappedseq.insertCharAt(8, al.getGapCharacter());
-    
+
     // create an alignment view with the gapped sequence
     SequenceI[] seqs = new SequenceI[1];
     seqs[0] = gappedseq;
@@ -716,19 +800,654 @@ public class HiddenColumnsTest
             false);
 
     // confirm that original contigs are as expected
-    int[] oldcontigs = hidden.getVisibleContigs(0, 20);
-    int[] testcontigs = { 0, 14, 18, 19 };
-    assertTrue(Arrays.equals(oldcontigs, testcontigs));
-            
+    Iterator<int[]> visible = hidden.getVisContigsIterator(0, 25);
+    int[] region = visible.next();
+    assertEquals("[0, 14]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[18, 24]", Arrays.toString(region));
+
     // propagate insertions
     HiddenColumns result = HiddenColumns.propagateInsertions(profileseq, al,
             view);
 
     // confirm that the contigs have changed to account for the gaps
-    int[] newcontigs = result.getVisibleContigs(0, 20);
-    testcontigs[1] = 10;
-    testcontigs[2] = 14;
-    assertTrue(Arrays.equals(newcontigs, testcontigs));
-    
+    visible = result.getVisContigsIterator(0, 25);
+    region = visible.next();
+    assertEquals("[0, 10]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[14, 24]", Arrays.toString(region));
+
+    // confirm the alignment has been changed so that the other sequences have
+    // gaps inserted where the columns are hidden
+    assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[10]));
+    assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[11]));
+    assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[12]));
+    assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[13]));
+    assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[14]));
+
+  }
+
+  @Test(groups = "Functional")
+  public void testPropagateInsertionsOverlap()
+  {
+    // test propagateInsertions where gaps and hiddenColumns overlap
+
+    // create an alignment with no gaps - this will be the profile seq and other
+    // JPRED seqs
+    AlignmentGenerator gen = new AlignmentGenerator(false);
+    AlignmentI al = gen.generate(20, 10, 1234, 0, 0);
+
+    // get the profileseq
+    SequenceI profileseq = al.getSequenceAt(0);
+    SequenceI gappedseq = new Sequence(profileseq);
+    gappedseq.insertCharAt(5, al.getGapCharacter());
+    gappedseq.insertCharAt(6, al.getGapCharacter());
+    gappedseq.insertCharAt(7, al.getGapCharacter());
+    gappedseq.insertCharAt(8, al.getGapCharacter());
+
+    // create an alignment view with the gapped sequence
+    SequenceI[] seqs = new SequenceI[1];
+    seqs[0] = gappedseq;
+    AlignmentI newal = new Alignment(seqs);
+
+    // hide columns so that some overlap with the gaps
+    HiddenColumns hidden = new HiddenColumns();
+    hidden.hideColumns(7, 10);
+
+    AlignmentView view = new AlignmentView(newal, hidden, null, true, false,
+            false);
+
+    // confirm that original contigs are as expected
+    Iterator<int[]> visible = hidden.getVisContigsIterator(0, 20);
+    int[] region = visible.next();
+    assertEquals("[0, 6]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[11, 19]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // propagate insertions
+    HiddenColumns result = HiddenColumns.propagateInsertions(profileseq, al,
+            view);
+
+    // confirm that the contigs have changed to account for the gaps
+    visible = result.getVisContigsIterator(0, 20);
+    region = visible.next();
+    assertEquals("[0, 4]", Arrays.toString(region));
+    region = visible.next();
+    assertEquals("[7, 19]", Arrays.toString(region));
+    assertFalse(visible.hasNext());
+
+    // confirm the alignment has been changed so that the other sequences have
+    // gaps inserted where the columns are hidden
+    assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[4]));
+    assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[5]));
+    assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[6]));
+    assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[7]));
+  }
+
+  @Test(groups = "Functional")
+  public void testHasHiddenColumns()
+  {
+    HiddenColumns h = new HiddenColumns();
+
+    // new HiddenColumns2 has no hidden cols
+    assertFalse(h.hasHiddenColumns());
+
+    // some columns hidden, returns true
+    h.hideColumns(5, 10);
+    assertTrue(h.hasHiddenColumns());
+
+    // reveal columns, no hidden cols again
+    ColumnSelection sel = new ColumnSelection();
+    h.revealAllHiddenColumns(sel);
+    assertFalse(h.hasHiddenColumns());
+  }
+
+  @Test(groups = "Functional")
+  public void testHasManyHiddenColumns()
+  {
+    HiddenColumns h = new HiddenColumns();
+
+    // new HiddenColumns2 has no hidden cols
+    assertFalse(h.hasManyHiddenColumns());
+
+    // one set of columns hidden, returns false
+    h.hideColumns(5, 10);
+    assertFalse(h.hasManyHiddenColumns());
+
+    // two sets hidden, returns true
+    h.hideColumns(15, 17);
+    assertTrue(h.hasManyHiddenColumns());
+
+    // back to one block, asserts false
+    h.hideColumns(11, 14);
+    assertFalse(h.hasManyHiddenColumns());
+  }
+
+  @Test(groups = "Functional")
+  public void testAdjustForHiddenColumns()
+  {
+    HiddenColumns h = new HiddenColumns();
+    // returns input value when there are no hidden columns
+    assertEquals(10, h.adjustForHiddenColumns(10));
+
+    h.hideColumns(20, 30);
+    assertEquals(10, h.adjustForHiddenColumns(10));
+    assertEquals(20 + 11, h.adjustForHiddenColumns(20));
+    assertEquals(35 + 11, h.adjustForHiddenColumns(35));
+
+    h.hideColumns(5, 7);
+    assertEquals(10 + 3, h.adjustForHiddenColumns(10));
+    assertEquals(20 + 14, h.adjustForHiddenColumns(20));
+    assertEquals(35 + 14, h.adjustForHiddenColumns(35));
+
+    ColumnSelection sel = new ColumnSelection();
+    h.revealAllHiddenColumns(sel);
+    h.hideColumns(0, 1);
+    assertEquals(4, h.adjustForHiddenColumns(2));
+  }
+
+  @Test(groups = "Functional")
+  public void testGetHiddenBoundaryLeft()
+  {
+    HiddenColumns h = new HiddenColumns();
+
+    // returns same value if no hidden cols
+    assertEquals(3, h.getHiddenBoundaryLeft(3));
+
+    h.hideColumns(5, 10);
+    assertEquals(10, h.getHiddenBoundaryLeft(15));
+    assertEquals(3, h.getHiddenBoundaryLeft(3));
+    assertEquals(7, h.getHiddenBoundaryLeft(7));
+
+    h.hideColumns(15, 20);
+    assertEquals(10, h.getHiddenBoundaryLeft(15));
+    assertEquals(20, h.getHiddenBoundaryLeft(21));
+  }
+
+  @Test(groups = "Functional")
+  public void testGetHiddenBoundaryRight()
+  {
+    HiddenColumns h = new HiddenColumns();
+
+    // returns same value if no hidden cols
+    assertEquals(3, h.getHiddenBoundaryRight(3));
+
+    h.hideColumns(5, 10);
+    assertEquals(5, h.getHiddenBoundaryRight(3));
+    assertEquals(15, h.getHiddenBoundaryRight(15));
+    assertEquals(7, h.getHiddenBoundaryRight(7));
+
+    h.hideColumns(15, 20);
+    assertEquals(15, h.getHiddenBoundaryRight(7));
+    assertEquals(15, h.getHiddenBoundaryRight(14));
+  }
+
+  @Test(groups = "Functional")
+  public void testIterator()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Iterator<int[]> result = h.iterator();
+    assertFalse(result.hasNext());
+
+    h.hideColumns(5, 10);
+    result = h.iterator();
+    int[] next = result.next();
+    assertEquals(5, next[0]);
+    assertEquals(10, next[1]);
+    assertFalse(result.hasNext());
+
+    h.hideColumns(22, 23);
+    result = h.iterator();
+    next = result.next();
+    assertEquals(5, next[0]);
+    assertEquals(10, next[1]);
+    next = result.next();
+    assertEquals(22, next[0]);
+    assertEquals(23, next[1]);
+    assertFalse(result.hasNext());
+
+    // test for only one hidden region at start of alignment
+    ColumnSelection sel = new ColumnSelection();
+    h.revealAllHiddenColumns(sel);
+    h.hideColumns(0, 1);
+    result = h.iterator();
+    next = result.next();
+    assertEquals(0, next[0]);
+    assertEquals(1, next[1]);
+    assertFalse(result.hasNext());
+  }
+
+  @Test(groups = "Functional")
+  public void testGetVisibleSequenceStrings()
+  {
+    HiddenColumns h = new HiddenColumns();
+    SequenceI seq1 = new Sequence("TEST1", "GALMFWKQESPVICYHRNDT");
+    SequenceI seq2 = new Sequence("TEST2", "VICYHRNDTGA");
+    SequenceI[] seqs = new SequenceI[2];
+    seqs[0] = seq1;
+    seqs[1] = seq2;
+    String[] result = h.getVisibleSequenceStrings(5, 10, seqs);
+    assertEquals(2, result.length);
+    assertEquals("WKQES", result[0]);
+    assertEquals("RNDTG", result[1]);
+
+    h.hideColumns(6, 8);
+    result = h.getVisibleSequenceStrings(5, 10, seqs);
+    assertEquals(2, result.length);
+    assertEquals("WS", result[0]);
+    assertEquals("RG", result[1]);
+
+    SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---");
+    ColumnSelection sel = new ColumnSelection();
+    h.revealAllHiddenColumns(sel);
+    h.hideColumns(1, 3);
+    h.hideColumns(6, 11);
+    assertEquals("-D",
+            h.getVisibleSequenceStrings(0, 5, new SequenceI[]
+    { seq })[0]);
+  }
+
+  @Test(groups = "Functional")
+  public void testHideInsertionsFor()
+  {
+    HiddenColumns h = new HiddenColumns();
+    HiddenColumns h2 = new HiddenColumns();
+    SequenceI seq1 = new Sequence("TEST1", "GAL---MFW-KQESPVICY--HRNDT");
+    SequenceI seq2 = new Sequence("TEST1", "GALMFWKQESPVICYHRNDT");
+
+    h.hideInsertionsFor(seq2);
+    assertTrue(h.equals(h2));
+
+    h.hideInsertionsFor(seq1);
+    h2.hideColumns(3, 5);
+    h2.hideColumns(9, 9);
+    h2.hideColumns(19, 20);
+    assertTrue(h.equals(h2));
+  }
+
+  @Test(groups = "Functional")
+  public void testHideMarkedBits()
+  {
+    HiddenColumns h = new HiddenColumns();
+    HiddenColumns h2 = new HiddenColumns();
+
+    BitSet tohide = new BitSet(21);
+    h.hideMarkedBits(tohide);
+    assertTrue(h.equals(h2));
+
+    // NB in hideMarkedBits, the last bit is not set to hidden
+    tohide.set(3, 6);
+    tohide.set(9);
+    tohide.set(19, 21);
+    h.hideMarkedBits(tohide);
+
+    h2.hideColumns(3, 5);
+    h2.hideColumns(9, 9);
+    h2.hideColumns(19, 20);
+    assertTrue(h.equals(h2));
+  }
+
+  @Test(groups = "Functional")
+  public void testMakeVisibleAnnotation()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Annotation[] anns = new Annotation[] { null, null, new Annotation(1),
+        new Annotation(2), new Annotation(3), null, null, new Annotation(4),
+        new Annotation(5), new Annotation(6), new Annotation(7),
+        new Annotation(8) };
+    AlignmentAnnotation ann = new AlignmentAnnotation("an", "some an",
+            anns);
+
+    // without hidden cols, just truncates
+    h.makeVisibleAnnotation(3, 5, ann);
+    assertEquals(3, ann.annotations.length);
+    assertEquals(2.0f, ann.annotations[0].value);
+    assertEquals(3.0f, ann.annotations[1].value);
+    assertNull(ann.annotations[2]);
+
+    anns = new Annotation[] { null, null, new Annotation(1),
+        new Annotation(2), new Annotation(3), null, null, new Annotation(4),
+        new Annotation(5), new Annotation(6), new Annotation(7),
+        new Annotation(8) };
+    ann = new AlignmentAnnotation("an", "some an", anns);
+    h.hideColumns(4, 7);
+    h.makeVisibleAnnotation(1, 9, ann);
+    assertEquals(5, ann.annotations.length);
+    assertNull(ann.annotations[0]);
+    assertEquals(1.0f, ann.annotations[1].value);
+    assertEquals(2.0f, ann.annotations[2].value);
+    assertEquals(5.0f, ann.annotations[3].value);
+    assertEquals(6.0f, ann.annotations[4].value);
+
+    anns = new Annotation[] { null, null, new Annotation(1),
+        new Annotation(2), new Annotation(3), null, null, new Annotation(4),
+        new Annotation(5), new Annotation(6), new Annotation(7),
+        new Annotation(8) };
+    ann = new AlignmentAnnotation("an", "some an", anns);
+    h.hideColumns(1, 2);
+    h.makeVisibleAnnotation(1, 9, ann);
+    assertEquals(3, ann.annotations.length);
+    assertEquals(2.0f, ann.annotations[0].value);
+    assertEquals(5.0f, ann.annotations[1].value);
+    assertEquals(6.0f, ann.annotations[2].value);
+  }
+
+  @Test(groups = "Functional")
+  public void testSubtractVisibleColumns()
+  {
+    HiddenColumns h = new HiddenColumns();
+    int result = h.subtractVisibleColumns(1, 10);
+    assertEquals(9, result);
+
+    h.hideColumns(7, 9);
+    result = h.subtractVisibleColumns(4, 10);
+    assertEquals(3, result);
+
+    h.hideColumns(14, 15);
+    result = h.subtractVisibleColumns(4, 10);
+    assertEquals(3, result);
+
+    result = h.subtractVisibleColumns(10, 17);
+    assertEquals(2, result);
+
+    result = h.subtractVisibleColumns(1, 7);
+    assertEquals(5, result);
+
+    result = h.subtractVisibleColumns(1, 8);
+    assertEquals(5, result);
+
+    result = h.subtractVisibleColumns(3, 15);
+    assertEquals(10, result);
+
+    ColumnSelection sel = new ColumnSelection();
+    h.revealAllHiddenColumns(sel);
+    h.hideColumns(0, 30);
+    result = h.subtractVisibleColumns(31, 0);
+    assertEquals(-31, result);
+
+    HiddenColumns cs = new HiddenColumns();
+
+    // test that without hidden columns, findColumnNToLeft returns
+    // position n to left of provided position
+    long pos = cs.subtractVisibleColumns(3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = cs.subtractVisibleColumns(0, 10);
+    assertEquals(10, pos);
+
+    // overflow to left returns negative number
+    pos = cs.subtractVisibleColumns(3, 0);
+    assertEquals(-3, pos);
+
+    // test that with hidden columns to left of result column
+    // behaviour is the same as above
+    cs.hideColumns(1, 3);
+
+    // position n to left of provided position
+    pos = cs.subtractVisibleColumns(3, 10);
+    assertEquals(7, pos);
+
+    // 0 returns same position
+    pos = cs.subtractVisibleColumns(0, 10);
+    assertEquals(10, pos);
+
+    // test with one set of hidden columns between start and required position
+    cs.hideColumns(12, 15);
+    pos = cs.subtractVisibleColumns(8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden columns between start and required position
+    cs.hideColumns(20, 21);
+    pos = cs.subtractVisibleColumns(8, 23);
+    assertEquals(9, pos);
+
+    // repeat last 2 tests with no hidden columns to left of required position
+    ColumnSelection colsel = new ColumnSelection();
+    cs.revealAllHiddenColumns(colsel);
+
+    // test with one set of hidden columns between start and required position
+    cs.hideColumns(12, 15);
+    pos = cs.subtractVisibleColumns(8, 17);
+    assertEquals(5, pos);
+
+    // test with two sets of hidden columns between start and required position
+    cs.hideColumns(20, 21);
+    pos = cs.subtractVisibleColumns(8, 23);
+    assertEquals(9, pos);
+
+  }
+
+
+  @Test(groups = "Functional")
+  public void testBoundedIterator()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Iterator<int[]> it = h.getBoundedIterator(0, 10);
+
+    // no hidden columns = nothing to iterate over
+    assertFalse(it.hasNext());
+
+    // [start,end] contains all hidden columns
+    // all regions are returned
+    h.hideColumns(3, 10);
+    h.hideColumns(14, 16);
+    it = h.getBoundedIterator(0, 20);
+    assertTrue(it.hasNext());
+    int[] next = it.next();
+    assertEquals(3, next[0]);
+    assertEquals(10, next[1]);
+    next = it.next();
+    assertEquals(14, next[0]);
+    assertEquals(16, next[1]);
+    assertFalse(it.hasNext());
+
+    // [start,end] overlaps a region
+    // 1 region returned
+    it = h.getBoundedIterator(5, 7);
+    assertTrue(it.hasNext());
+    next = it.next();
+    assertEquals(3, next[0]);
+    assertEquals(10, next[1]);
+    assertFalse(it.hasNext());
+
+    // [start,end] fully contains 1 region and start of last
+    // - 2 regions returned
+    it = h.getBoundedIterator(3, 15);
+    assertTrue(it.hasNext());
+    next = it.next();
+    assertEquals(3, next[0]);
+    assertEquals(10, next[1]);
+    next = it.next();
+    assertEquals(14, next[0]);
+    assertEquals(16, next[1]);
+    assertFalse(it.hasNext());
+
+    // [start,end] contains end of first region and whole of last region
+    // - 2 regions returned
+    it = h.getBoundedIterator(4, 20);
+    assertTrue(it.hasNext());
+    next = it.next();
+    assertEquals(3, next[0]);
+    assertEquals(10, next[1]);
+    next = it.next();
+    assertEquals(14, next[0]);
+    assertEquals(16, next[1]);
+    assertFalse(it.hasNext());
+  }
+
+  @Test(groups = "Functional")
+  public void testBoundedStartIterator()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Iterator<Integer> it = h.getBoundedStartIterator(0, 10);
+
+    // no hidden columns = nothing to iterate over
+    assertFalse(it.hasNext());
+
+    // [start,end] contains all hidden columns
+    // all regions are returned
+    h.hideColumns(3, 10);
+    h.hideColumns(14, 16);
+    it = h.getBoundedStartIterator(0, 20);
+    assertTrue(it.hasNext());
+    int next = it.next();
+    assertEquals(3, next);
+    next = it.next();
+    assertEquals(6, next);
+    assertFalse(it.hasNext());
+
+    // [start,end] does not contain a start of a region
+    // no regions to iterate over
+    it = h.getBoundedStartIterator(4, 5);
+    assertFalse(it.hasNext());
+
+    // [start,end] fully contains 1 region and start of last
+    // - 2 regions returned
+    it = h.getBoundedStartIterator(3, 7);
+    assertTrue(it.hasNext());
+    next = it.next();
+    assertEquals(3, next);
+    next = it.next();
+    assertEquals(6, next);
+    assertFalse(it.hasNext());
+
+    // [start,end] contains whole of last region
+    // - 1 region returned
+    it = h.getBoundedStartIterator(4, 20);
+    assertTrue(it.hasNext());
+    next = it.next();
+    assertEquals(6, next);
+    assertFalse(it.hasNext());
+  }
+
+  @Test(groups = "Functional")
+  public void testVisibleBlocksVisBoundsIterator()
+  {
+    HiddenColumns h = new HiddenColumns();
+    Iterator<int[]> regions = h.getVisibleBlocksIterator(0, 30, true);
+
+    // only 1 visible region spanning 0-30 if nothing is hidden
+    assertTrue(regions.hasNext());
+    int[] region = regions.next();
+    assertEquals(0, region[0]);
+    assertEquals(30, region[1]);
+    assertFalse(regions.hasNext());
+
+    // hide 1 region in middle
+    // 2 regions one on either side
+    // second region boundary accounts for hidden columns
+    h.hideColumns(10, 15);
+    regions = h.getVisibleBlocksIterator(0, 30, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(0, region[0]);
+    assertEquals(9, region[1]);
+    region = regions.next();
+    assertEquals(16, region[0]);
+    assertEquals(36, region[1]);
+    assertFalse(regions.hasNext());
+
+    // single hidden region at left
+    h = new HiddenColumns();
+    h.hideColumns(0, 5);
+    regions = h.getVisibleBlocksIterator(0, 30, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(6, region[0]);
+    assertEquals(36, region[1]);
+    assertFalse(regions.hasNext());
+
+    // single hidden region at right
+    h = new HiddenColumns();
+    h.hideColumns(27, 30);
+    regions = h.getVisibleBlocksIterator(0, 30, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(0, region[0]);
+    assertEquals(26, region[1]);
+    region = regions.next();
+    assertEquals(31, region[0]);
+    assertEquals(34, region[1]);
+    assertFalse(regions.hasNext());
+
+    // hidden region at left + hidden region in middle
+    h = new HiddenColumns();
+    h.hideColumns(0, 5);
+    h.hideColumns(23, 25);
+    regions = h.getVisibleBlocksIterator(0, 30, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(6, region[0]);
+    assertEquals(22, region[1]);
+    region = regions.next();
+    assertEquals(26, region[0]);
+    assertEquals(39, region[1]);
+    assertFalse(regions.hasNext());
+
+    // hidden region at right + hidden region in middle
+    h = new HiddenColumns();
+    h.hideColumns(27, 30);
+    h.hideColumns(11, 14);
+    regions = h.getVisibleBlocksIterator(0, 30, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(0, region[0]);
+    assertEquals(10, region[1]);
+    region = regions.next();
+    assertEquals(15, region[0]);
+    assertEquals(26, region[1]);
+    region = regions.next();
+    assertEquals(31, region[0]);
+    assertEquals(38, region[1]);
+    assertFalse(regions.hasNext());
+
+    // hidden region at left and right
+    h = new HiddenColumns();
+    h.hideColumns(27, 35);
+    h.hideColumns(0, 4);
+    regions = h.getVisibleBlocksIterator(0, 30, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(5, region[0]);
+    assertEquals(26, region[1]);
+    region = regions.next();
+    assertEquals(36, region[0]);
+    assertEquals(44, region[1]);
+    assertFalse(regions.hasNext());
+
+    // multiple hidden regions
+    h = new HiddenColumns();
+    h.hideColumns(1, 1);
+    h.hideColumns(3, 5);
+    h.hideColumns(9, 11);
+    h.hideColumns(22, 26);
+
+    regions = h.getVisibleBlocksIterator(0, 30, true);
+
+    assertTrue(regions.hasNext());
+    region = regions.next();
+    assertEquals(0, region[0]);
+    assertEquals(0, region[1]);
+    region = regions.next();
+    assertEquals(2, region[0]);
+    assertEquals(2, region[1]);
+    region = regions.next();
+    assertEquals(6, region[0]);
+    assertEquals(8, region[1]);
+    region = regions.next();
+    assertEquals(12, region[0]);
+    assertEquals(21, region[1]);
+    region = regions.next();
+    assertEquals(27, region[0]);
+    assertEquals(42, region[1]);
+    assertFalse(regions.hasNext());
   }
 }
index c0cb09c..549f13a 100644 (file)
@@ -41,13 +41,13 @@ import java.util.BitSet;
 import java.util.List;
 import java.util.Vector;
 
-import junit.extensions.PA;
-
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import junit.extensions.PA;
+
 public class SequenceTest
 {
 
@@ -799,7 +799,7 @@ public class SequenceTest
     Assert.assertEquals(pdbe1a,
             sq.getDatasetSequence().getPDBEntry("1PDB"),
             "PDB Entry '1PDB' not found on dataset sequence via getPDBEntry.");
-    ArrayList<Annotation> annotsList = new ArrayList<Annotation>();
+    ArrayList<Annotation> annotsList = new ArrayList<>();
     System.out.println(">>>>>> " + sq.getSequenceAsString().length());
     annotsList.add(new Annotation("A", "A", 'X', 0.1f));
     annotsList.add(new Annotation("A", "A", 'X', 0.1f));
@@ -1674,6 +1674,20 @@ public class SequenceTest
   }
 
   @Test(groups = { "Functional" })
+  public void testGapBitset()
+  {
+    SequenceI sq = new Sequence("test/8-13", "-ABC---DE-F--");
+    BitSet bs = sq.gapBitset();
+    BitSet expected = new BitSet();
+    expected.set(0);
+    expected.set(4, 7);
+    expected.set(9);
+    expected.set(11, 13);
+
+    assertTrue(bs.equals(expected));
+
+  }
+
   public void testFindFeatures_largeEndPos()
   {
     /*
index b2d747b..f9b01bd 100644 (file)
@@ -22,6 +22,7 @@ package jalview.datamodel;
 
 import static org.testng.Assert.assertTrue;
 
+import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 import org.testng.annotations.BeforeClass;
@@ -50,11 +51,12 @@ public class VisibleColsIteratorTest
   @Test(groups = { "Functional" })
   public void testHasNextAndNextWithHidden()
   {
-    VisibleColsIterator it = new VisibleColsIterator(0, 6, hiddenCols);
+    Iterator<Integer> it = hiddenCols.getVisibleColsIterator(0, 6);
     int count = 0;
     while (it.hasNext())
     {
-      it.next();
+      int result = it.next();
+      System.out.println(result);
       count++;
     }
     assertTrue(count == 4, "hasNext() is false after 4 iterations");
@@ -67,8 +69,8 @@ public class VisibleColsIteratorTest
   @Test(groups = { "Functional" })
   public void testHasNextAndNextNoHidden()
   {
-    VisibleColsIterator it2 = new VisibleColsIterator(0, 3,
-            new HiddenColumns());
+    HiddenColumns test = new HiddenColumns();
+    Iterator<Integer> it2 = test.getVisibleColsIterator(0, 3);
     int count = 0;
     while (it2.hasNext())
     {
@@ -85,8 +87,7 @@ public class VisibleColsIteratorTest
   @Test(groups = { "Functional" })
   public void testHasNextAndNextStartHidden()
   {
-    VisibleColsIterator it3 = new VisibleColsIterator(0, 6,
-            hiddenColsAtStart);
+    Iterator<Integer> it3 = hiddenColsAtStart.getVisibleColsIterator(0, 6);
     int count = 0;
     while (it3.hasNext())
     {
@@ -103,7 +104,7 @@ public class VisibleColsIteratorTest
   @Test(groups = { "Functional" })
   public void testHasNextAndNextEndHidden()
   {
-    VisibleColsIterator it4 = new VisibleColsIterator(0, 4, hiddenCols);
+    Iterator<Integer> it4 = hiddenCols.getVisibleColsIterator(0, 4);
     int count = 0;
     while (it4.hasNext())
     {
@@ -123,7 +124,7 @@ public class VisibleColsIteratorTest
     expectedExceptions = { NoSuchElementException.class })
   public void testLastNextWithHidden() throws NoSuchElementException
   {
-    VisibleColsIterator it = new VisibleColsIterator(0, 3, hiddenCols);
+    Iterator<Integer> it = hiddenCols.getVisibleColsIterator(0, 3);
     while (it.hasNext())
     {
       it.next();
@@ -140,8 +141,8 @@ public class VisibleColsIteratorTest
     expectedExceptions = { NoSuchElementException.class })
   public void testLastNextNoHidden() throws NoSuchElementException
   {
-    VisibleColsIterator it2 = new VisibleColsIterator(0, 3,
-            new HiddenColumns());
+    HiddenColumns test = new HiddenColumns();
+    Iterator<Integer> it2 = test.getVisibleColsIterator(0, 3);
     while (it2.hasNext())
     {
       it2.next();
@@ -158,8 +159,7 @@ public class VisibleColsIteratorTest
     expectedExceptions = { NoSuchElementException.class })
   public void testLastNextStartHidden() throws NoSuchElementException
   {
-    VisibleColsIterator it3 = new VisibleColsIterator(0, 6,
-            hiddenColsAtStart);
+    Iterator<Integer> it3 = hiddenColsAtStart.getVisibleColsIterator(0, 6);
     while (it3.hasNext())
     {
       it3.next();
@@ -176,7 +176,7 @@ public class VisibleColsIteratorTest
     expectedExceptions = { NoSuchElementException.class })
   public void testLastNextEndHidden() throws NoSuchElementException
   {
-    VisibleColsIterator it4 = new VisibleColsIterator(0, 4, hiddenCols);
+    Iterator<Integer> it4 = hiddenCols.getVisibleColsIterator(0, 4);
     while (it4.hasNext())
     {
       it4.next();
@@ -192,7 +192,7 @@ public class VisibleColsIteratorTest
     expectedExceptions = { UnsupportedOperationException.class })
   public void testRemove() throws UnsupportedOperationException
   {
-    VisibleColsIterator it = new VisibleColsIterator(0, 3, hiddenCols);
+    Iterator<Integer> it = hiddenCols.getVisibleColsIterator(0, 3);
     it.remove();
   }
 }
index af9c045..dd1a4de 100644 (file)
@@ -46,7 +46,7 @@ import jalview.schemes.TurnColourScheme;
 import jalview.util.MessageManager;
 
 import java.awt.Color;
-import java.util.List;
+import java.util.Iterator;
 
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
@@ -85,24 +85,20 @@ public class AlignFrameTest
      */
     assertFalse(alignFrame.hideFeatureColumns("exon", true));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-    assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns()
-            .getHiddenColumnsCopy()
-            .isEmpty());
+    assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getNumberOfRegions(), 0);
     assertFalse(alignFrame.hideFeatureColumns("exon", false));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-    assertTrue(alignFrame.getViewport().getAlignment().getHiddenColumns()
-            .getHiddenColumnsCopy()
-            .isEmpty());
+    assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getNumberOfRegions(), 0);
 
     /*
      * hiding a feature in all columns does nothing
      */
     assertFalse(alignFrame.hideFeatureColumns("Metal", true));
     assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-    List<int[]> hidden = alignFrame.getViewport().getAlignment()
-            .getHiddenColumns()
-            .getHiddenColumnsCopy();
-    assertTrue(hidden.isEmpty());
+    assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getNumberOfRegions(), 0);
 
     /*
      * hide a feature present in some columns
@@ -110,13 +106,16 @@ public class AlignFrameTest
      * [1-3], [6-8] base zero
      */
     assertTrue(alignFrame.hideFeatureColumns("Turn", true));
-    hidden = alignFrame.getViewport().getAlignment().getHiddenColumns()
-            .getHiddenColumnsCopy();
-    assertEquals(hidden.size(), 2);
-    assertEquals(hidden.get(0)[0], 1);
-    assertEquals(hidden.get(0)[1], 3);
-    assertEquals(hidden.get(1)[0], 6);
-    assertEquals(hidden.get(1)[1], 8);
+    Iterator<int[]> regions = alignFrame.getViewport().getAlignment()
+            .getHiddenColumns().iterator();
+    assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+            .getNumberOfRegions(), 2);
+    int[] next = regions.next();
+    assertEquals(next[0], 1);
+    assertEquals(next[1], 3);
+    next = regions.next();
+    assertEquals(next[0], 6);
+    assertEquals(next[1], 8);
   }
 
   @BeforeClass(alwaysRun = true)
diff --git a/test/jalview/gui/AnnotationColumnChooserTest.java b/test/jalview/gui/AnnotationColumnChooserTest.java
new file mode 100644 (file)
index 0000000..912cd27
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.gui;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.AssertJUnit.assertEquals;
+
+import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
+import jalview.bin.Cache;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SequenceI;
+import jalview.io.DataSourceType;
+import jalview.io.FileFormat;
+import jalview.io.FormatAdapter;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests for AnnotationChooser
+ * 
+ * @author kmourao
+ *
+ */
+public class AnnotationColumnChooserTest
+{
+  @BeforeClass(alwaysRun = true)
+  public void setUpJvOptionPane()
+  {
+    JvOptionPane.setInteractiveMode(false);
+    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+  }
+
+  // 4 sequences x 13 positions
+  final static String TEST_DATA = ">FER_CAPAA Ferredoxin\n"
+          + "TIETHKEAELVG-\n"
+          + ">FER_CAPAN Ferredoxin, chloroplast precursor\n"
+          + "TIETHKEAELVG-\n"
+          + ">FER1_SOLLC Ferredoxin-1, chloroplast precursor\n"
+          + "TIETHKEEELTA-\n" + ">Q93XJ9_SOLTU Ferredoxin I precursor\n"
+          + "TIETHKEEELTA-\n";
+
+  AnnotationChooser testee;
+
+  AlignmentPanel parentPanel;
+
+  AlignFrame af;
+
+  @BeforeMethod(alwaysRun = true)
+  public void setUp() throws IOException
+  {
+    Cache.loadProperties("test/jalview/io/testProps.jvprops");
+    // pin down annotation sort order for test
+    Cache.applicationProperties.setProperty(Preferences.SORT_ANNOTATIONS,
+            SequenceAnnotationOrder.NONE.name());
+    final String TRUE = Boolean.TRUE.toString();
+    Cache.applicationProperties.setProperty(Preferences.SHOW_AUTOCALC_ABOVE,
+            TRUE);
+    Cache.applicationProperties.setProperty("SHOW_QUALITY", TRUE);
+    Cache.applicationProperties.setProperty("SHOW_CONSERVATION", TRUE);
+    Cache.applicationProperties.setProperty("SHOW_IDENTITY", TRUE);
+
+    AlignmentI al = new FormatAdapter().readFile(TEST_DATA,
+            DataSourceType.PASTE, FileFormat.Fasta);
+    af = new AlignFrame(al, 700, 500);
+    parentPanel = new AlignmentPanel(af, af.getViewport());
+    addAnnotations();
+  }
+
+  /**
+   * Add 4 annotations, 3 of them sequence-specific.
+   * 
+   * <PRE>
+   * ann1 - for sequence 0 - label 'IUPRED' ann2 - not sequence related - label
+   * 'Beauty' ann3 - for sequence 3 - label 'JMol' ann4 - for sequence 2 - label
+   * 'IUPRED' ann5 - for sequence 1 - label 'JMol'
+   */
+  private void addAnnotations()
+  {
+    Annotation an = new Annotation(2f);
+    Annotation[] anns = new Annotation[] { an, an, an };
+    AlignmentAnnotation ann0 = new AlignmentAnnotation("IUPRED", "", anns);
+    AlignmentAnnotation ann1 = new AlignmentAnnotation("Beauty", "", anns);
+    AlignmentAnnotation ann2 = new AlignmentAnnotation("JMol", "", anns);
+    AlignmentAnnotation ann3 = new AlignmentAnnotation("IUPRED", "", anns);
+    AlignmentAnnotation ann4 = new AlignmentAnnotation("JMol", "", anns);
+    SequenceI[] seqs = parentPanel.getAlignment().getSequencesArray();
+    ann0.setSequenceRef(seqs[0]);
+    ann2.setSequenceRef(seqs[3]);
+    ann3.setSequenceRef(seqs[2]);
+    ann4.setSequenceRef(seqs[1]);
+    parentPanel.getAlignment().addAnnotation(ann0);
+    parentPanel.getAlignment().addAnnotation(ann1);
+    parentPanel.getAlignment().addAnnotation(ann2);
+    parentPanel.getAlignment().addAnnotation(ann3);
+    parentPanel.getAlignment().addAnnotation(ann4);
+  }
+
+  /**
+   * Test reset
+   */
+  @Test(groups = { "Functional" })
+  public void testReset()
+  {
+    AnnotationColumnChooser acc = new AnnotationColumnChooser(
+            af.getViewport(), af.alignPanel);
+
+    HiddenColumns oldhidden = new HiddenColumns();
+    oldhidden.hideColumns(10, 20);
+    acc.setOldHiddenColumns(oldhidden);
+
+    HiddenColumns newHidden = new HiddenColumns();
+    newHidden.hideColumns(0, 3);
+    newHidden.hideColumns(22, 25);
+    af.getViewport().setHiddenColumns(newHidden);
+
+    HiddenColumns currentHidden = af.getViewport().getAlignment()
+            .getHiddenColumns();
+    Iterator<int[]> regions = currentHidden.iterator();
+    int[] next = regions.next();
+    assertEquals(0, next[0]);
+    assertEquals(3, next[1]);
+    next = regions.next();
+    assertEquals(22, next[0]);
+    assertEquals(25, next[1]);
+
+    // now reset hidden columns
+    acc.reset();
+    currentHidden = af.getViewport().getAlignment().getHiddenColumns();
+    regions = currentHidden.iterator();
+    next = regions.next();
+    assertEquals(10, next[0]);
+    assertEquals(20, next[1]);
+
+    // check works with empty hidden columns as old columns
+    oldhidden = new HiddenColumns();
+    acc.setOldHiddenColumns(oldhidden);
+    acc.reset();
+    currentHidden = af.getViewport().getAlignment().getHiddenColumns();
+    assertFalse(currentHidden.hasHiddenColumns());
+
+    // check works with empty hidden columns as new columns
+    oldhidden.hideColumns(10, 20);
+    acc.setOldHiddenColumns(oldhidden);
+    currentHidden = af.getViewport().getAlignment().getHiddenColumns();
+    assertFalse(currentHidden.hasHiddenColumns());
+
+    acc.reset();
+    currentHidden = af.getViewport().getAlignment().getHiddenColumns();
+    regions = currentHidden.iterator();
+    next = regions.next();
+    assertEquals(10, next[0]);
+    assertEquals(20, next[1]);
+  }
+}
index 158c901..5e835bf 100644 (file)
@@ -42,6 +42,7 @@ import jalview.schemes.ResidueColourScheme;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
@@ -205,7 +206,7 @@ public class JSONFileTest
     TEST_SEQ_HEIGHT = expectedSeqs.size();
     TEST_GRP_HEIGHT = expectedGrps.size();
     TEST_ANOT_HEIGHT = expectedAnnots.size();
-    TEST_CS_HEIGHT = expectedColSel.getHiddenColumnsCopy().size();
+    TEST_CS_HEIGHT = expectedColSel.getNumberOfRegions();
 
     exportSettings = new AlignExportSettingI()
     {
@@ -325,11 +326,12 @@ public class JSONFileTest
   {
     HiddenColumns cs = testJsonFile.getHiddenColumns();
     Assert.assertNotNull(cs);
-    Assert.assertNotNull(cs.getHiddenColumnsCopy());
-    List<int[]> hiddenCols = cs.getHiddenColumnsCopy();
-    Assert.assertEquals(hiddenCols.size(), TEST_CS_HEIGHT);
-    Assert.assertEquals(hiddenCols.get(0), expectedColSel
-            .getHiddenColumnsCopy().get(0),
+
+    Iterator<int[]> it = cs.iterator();
+    Iterator<int[]> colselit = expectedColSel.iterator();
+    Assert.assertTrue(it.hasNext());
+    Assert.assertEquals(cs.getNumberOfRegions(), TEST_CS_HEIGHT);
+    Assert.assertEquals(it.next(), colselit.next(),
             "Mismatched hidden columns!");
   }
 
index 5226819..022e2d6 100644 (file)
@@ -50,6 +50,7 @@ import java.awt.Color;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 
 import org.testng.annotations.BeforeClass;
@@ -913,9 +914,9 @@ public class MappingUtilsTest
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
     assertEquals("[]", dnaSelection.getSelected().toString());
-    List<int[]> hidden = dnaHidden.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[0, 4]", Arrays.toString(hidden.get(0)));
+    Iterator<int[]> regions = dnaHidden.iterator();
+    assertEquals(1, dnaHidden.getNumberOfRegions());
+    assertEquals("[0, 4]", Arrays.toString(regions.next()));
 
     /*
      * Column 1 in protein picks up Seq1/K which maps to cols 0-3 in dna
@@ -930,9 +931,9 @@ public class MappingUtilsTest
     proteinSelection.hideSelectedColumns(1, hiddenCols);
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
-    hidden = dnaHidden.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[0, 3]", Arrays.toString(hidden.get(0)));
+    regions = dnaHidden.iterator();
+    assertEquals(1, dnaHidden.getNumberOfRegions());
+    assertEquals("[0, 3]", Arrays.toString(regions.next()));
 
     /*
      * Column 2 in protein picks up gaps only - no mapping
@@ -944,7 +945,7 @@ public class MappingUtilsTest
     proteinSelection.hideSelectedColumns(2, hiddenCols);
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
-    assertTrue(dnaHidden.getHiddenColumnsCopy().isEmpty());
+    assertEquals(0, dnaHidden.getNumberOfRegions());
 
     /*
      * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns
@@ -959,9 +960,9 @@ public class MappingUtilsTest
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
     assertEquals("[0, 1, 2, 3]", dnaSelection.getSelected().toString());
-    hidden = dnaHidden.getHiddenColumnsCopy();
-    assertEquals(1, hidden.size());
-    assertEquals("[5, 10]", Arrays.toString(hidden.get(0)));
+    regions = dnaHidden.iterator();
+    assertEquals(1, dnaHidden.getNumberOfRegions());
+    assertEquals("[5, 10]", Arrays.toString(regions.next()));
 
     /*
      * Combine hiding columns 1 and 3 to get discontiguous hidden columns
@@ -974,10 +975,10 @@ public class MappingUtilsTest
     proteinSelection.hideSelectedColumns(3, hiddenCols);
     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
             proteinView, dnaView, dnaSelection, dnaHidden);
-    hidden = dnaHidden.getHiddenColumnsCopy();
-    assertEquals(2, hidden.size());
-    assertEquals("[0, 3]", Arrays.toString(hidden.get(0)));
-    assertEquals("[5, 10]", Arrays.toString(hidden.get(1)));
+    regions = dnaHidden.iterator();
+    assertEquals(2, dnaHidden.getNumberOfRegions());
+    assertEquals("[0, 3]", Arrays.toString(regions.next()));
+    assertEquals("[5, 10]", Arrays.toString(regions.next()));
   }
 
   @Test(groups = { "Functional" })