JAL-2089 patch broken merge to master for Release 2.10.0b1
[jalview.git] / src / jalview / datamodel / ColumnSelection.java
index 74c58b7..d651c0b 100644 (file)
@@ -45,19 +45,52 @@ public class ColumnSelection
     /*
      * list of selected columns (ordered by selection order, not column order)
      */
-    private List<Integer> order = new ArrayList<Integer>();
+    private List<Integer> order;
+
+    /*
+     * an unmodifiable view of the selected columns list
+     */
+    private List<Integer> _uorder;
 
     /**
      * bitfield for column selection - allows quick lookup
      */
-    private BitSet selected = new BitSet();
+    private BitSet selected;
+
+    /**
+     * Constructor
+     */
+    IntList()
+    {
+      order = new ArrayList<Integer>();
+      _uorder = Collections.unmodifiableList(order);
+      selected = new BitSet();
+    }
+
+    /**
+     * Copy constructor
+     * 
+     * @param other
+     */
+    IntList(IntList other)
+    {
+      this();
+      if (other != null)
+      {
+        int j = other.size();
+        for (int i = 0; i < j; i++)
+        {
+          add(other.elementAt(i));
+        }
+      }
+    }
 
     /**
      * adds a new column i to the selection - only if i is not already selected
      * 
      * @param i
      */
-    public void add(int i)
+    void add(int i)
     {
       if (!selected.get(i))
       {
@@ -66,13 +99,13 @@ public class ColumnSelection
       }
     }
 
-    public void clear()
+    void clear()
     {
       order.clear();
       selected.clear();
     }
 
-    public void remove(int col)
+    void remove(int col)
     {
 
       Integer colInt = new Integer(col);
@@ -87,22 +120,27 @@ public class ColumnSelection
       }
     }
 
-    public boolean contains(Integer colInt)
+    boolean contains(Integer colInt)
     {
       return selected.get(colInt);
     }
 
-    public boolean isEmpty()
+    boolean isEmpty()
     {
       return order.isEmpty();
     }
 
-    public List<Integer> getList()
+    /**
+     * Returns a read-only view of the selected columns list
+     * 
+     * @return
+     */
+    List<Integer> getList()
     {
-      return order;
+      return _uorder;
     }
 
-    public int size()
+    int size()
     {
       return order.size();
     }
@@ -113,7 +151,7 @@ public class ColumnSelection
      * @param i
      * @return
      */
-    public int elementAt(int i)
+    int elementAt(int i)
     {
       return order.get(i);
     }
@@ -156,7 +194,7 @@ public class ColumnSelection
      * @param change
      *          - delta for shift
      */
-    public void compensateForEdits(int start, int change)
+    void compensateForEdits(int start, int change)
     {
       BitSet mask = new BitSet();
       for (int i = 0; i < order.size(); i++)
@@ -175,17 +213,17 @@ public class ColumnSelection
       selected.or(mask);
     }
 
-    public boolean isSelected(int column)
+    boolean isSelected(int column)
     {
       return selected.get(column);
     }
 
-    public int getMaxColumn()
+    int getMaxColumn()
     {
       return selected.length() - 1;
     }
 
-    public int getMinColumn()
+    int getMinColumn()
     {
       return selected.get(0) ? 0 : selected.nextSetBit(0);
     }
@@ -193,7 +231,7 @@ public class ColumnSelection
     /**
      * @return a series of selection intervals along the range
      */
-    public List<int[]> getRanges()
+    List<int[]> getRanges()
     {
       List<int[]> rlist = new ArrayList<int[]>();
       if (selected.isEmpty())
@@ -288,9 +326,14 @@ public class ColumnSelection
   }
 
   /**
-   * Returns a list of selected columns. The list contains no duplicates but is
-   * not necessarily ordered. It also may include columns hidden from the
-   * current view
+   * Returns a read-only view of the (possibly empty) list of selected columns
+   * <p>
+   * The list contains no duplicates but is not necessarily ordered. It also may
+   * include columns hidden from the current view. To modify (for example sort)
+   * the list, you should first make a copy.
+   * <p>
+   * The list is not thread-safe: iterating over it could result in
+   * ConcurrentModificationException if it is modified by another thread.
    */
   public List<Integer> getSelected()
   {
@@ -948,14 +991,7 @@ public class ColumnSelection
   {
     if (copy != null)
     {
-      if (copy.selection != null)
-      {
-        selection = new IntList();
-        for (int i = 0, j = copy.selection.size(); i < j; i++)
-        {
-          selection.add(copy.selection.elementAt(i));
-        }
-      }
+      selection = new IntList(copy.selection);
       if (copy.hiddenColumns != null)
       {
         hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
@@ -985,7 +1021,7 @@ public class ColumnSelection
           SequenceI[] seqs)
   {
     int i, iSize = seqs.length;
-    String selection[] = new String[iSize];
+    String selections[] = new String[iSize];
     if (hiddenColumns != null && hiddenColumns.size() > 0)
     {
       for (i = 0; i < iSize; i++)
@@ -1027,18 +1063,18 @@ public class ColumnSelection
           visibleSeq.append(seqs[i].getSequence(blockStart, end));
         }
 
-        selection[i] = visibleSeq.toString();
+        selections[i] = visibleSeq.toString();
       }
     }
     else
     {
       for (i = 0; i < iSize; i++)
       {
-        selection[i] = seqs[i].getSequenceAsString(start, end);
+        selections[i] = seqs[i].getSequenceAsString(start, end);
       }
     }
 
-    return selection;
+    return selections;
   }
 
   /**
@@ -1112,9 +1148,9 @@ public class ColumnSelection
    */
   public int[] locateVisibleBoundsOfSequence(SequenceI seq)
   {
-    int fpos=seq.getStart(),lpos= seq.getEnd();
+    int fpos = seq.getStart(), lpos = seq.getEnd();
     int start = 0;
-    
+
     if (hiddenColumns == null || hiddenColumns.size() == 0)
     {
       int ifpos = seq.findIndex(fpos) - 1, ilpos = seq.findIndex(lpos) - 1;
@@ -1651,14 +1687,130 @@ public class ColumnSelection
     return hashCode;
   }
 
+  /**
+   * Answers true if comparing to a ColumnSelection with the same selected
+   * columns and hidden columns, else false
+   */
   @Override
   public boolean equals(Object obj)
   {
-    if (obj instanceof ColumnSelection)
+    if (!(obj instanceof ColumnSelection))
     {
-      return hashCode() == obj.hashCode();
+      return false;
     }
-    return false;
+    ColumnSelection that = (ColumnSelection) obj;
+
+    /*
+     * check columns selected are either both null, or match
+     */
+    if (this.selection == null)
+    {
+      if (that.selection != null)
+      {
+        return false;
+      }
+    }
+    if (!this.selection.equals(that.selection))
+    {
+      return false;
+    }
+
+    /*
+     * check hidden columns are either both null, or match
+     */
+    if (this.hiddenColumns == null)
+    {
+      return (that.hiddenColumns == null);
+    }
+    if (that.hiddenColumns == null
+            || that.hiddenColumns.size() != this.hiddenColumns.size())
+    {
+      return false;
+    }
+    int i = 0;
+    for (int[] thisRange : hiddenColumns)
+    {
+      int[] thatRange = that.hiddenColumns.get(i++);
+      if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
+      {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Updates the column selection depending on the parameters, and returns true
+   * if any change was made to the selection
+   * 
+   * @param markedColumns
+   *          a set identifying marked columns (base 0)
+   * @param startCol
+   *          the first column of the range to operate over (base 0)
+   * @param endCol
+   *          the last column of the range to operate over (base 0)
+   * @param invert
+   *          if true, deselect marked columns and select unmarked
+   * @param extendCurrent
+   *          if true, extend rather than replacing the current column selection
+   * @param toggle
+   *          if true, toggle the selection state of marked columns
+   * 
+   * @return
+   */
+  public boolean markColumns(BitSet markedColumns, int startCol,
+          int endCol, boolean invert, boolean extendCurrent, boolean toggle)
+  {
+    boolean changed = false;
+    if (!extendCurrent && !toggle)
+    {
+      changed = !this.isEmpty();
+      clear();
+    }
+    if (invert)
+    {
+      // invert only in the currently selected sequence region
+      int i = markedColumns.nextClearBit(startCol);
+      int ibs = markedColumns.nextSetBit(startCol);
+      while (i >= startCol && i <= endCol)
+      {
+        if (ibs < 0 || i < ibs)
+        {
+          changed = true;
+          if (toggle && contains(i))
+          {
+            removeElement(i++);
+          }
+          else
+          {
+            addElement(i++);
+          }
+        }
+        else
+        {
+          i = markedColumns.nextClearBit(ibs);
+          ibs = markedColumns.nextSetBit(i);
+        }
+      }
+    }
+    else
+    {
+      int i = markedColumns.nextSetBit(startCol);
+      while (i >= startCol && i <= endCol)
+      {
+        changed = true;
+        if (toggle && contains(i))
+        {
+          removeElement(i);
+        }
+        else
+        {
+          addElement(i);
+        }
+        i = markedColumns.nextSetBit(i + 1);
+      }
+    }
+    return changed;
   }
 
 }