Bug 548587 - Performance improvement for SelectionUtils
Change-Id: I39650ce929df946260a09f342a985bc6c78c57ee
Signed-off-by: Dirk Fauth <dirk.fauth@googlemail.com>
diff --git a/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/coordinate/PositionUtilTest.java b/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/coordinate/PositionUtilTest.java
index 76011cd..0f34858 100644
--- a/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/coordinate/PositionUtilTest.java
+++ b/org.eclipse.nebula.widgets.nattable.core.test/src/org/eclipse/nebula/widgets/nattable/coordinate/PositionUtilTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2012, 2018 Original authors and others.
+ * Copyright (c) 2012, 2019 Original authors and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -11,6 +11,7 @@
package org.eclipse.nebula.widgets.nattable.coordinate;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
@@ -102,4 +103,45 @@
}
}
+ @Test
+ public void shouldJoinContiguousRanges() {
+ Range r1 = new Range(2, 5);
+ Range r2 = new Range(5, 9);
+ Range r3 = new Range(9, 12);
+
+ assertEquals(new Range(2, 12), PositionUtil.joinConsecutiveRanges(Arrays.asList(r1, r2, r3)));
+ }
+
+ @Test
+ public void shouldNotJoinNotContiguousRanges() {
+ Range r1 = new Range(2, 5);
+ Range r2 = new Range(6, 9);
+ Range r3 = new Range(10, 12);
+
+ assertNull(PositionUtil.joinConsecutiveRanges(Arrays.asList(r1, r2, r3)));
+ }
+
+ @Test
+ public void shouldMergeContiguousRangesToOne() {
+ Range r1 = new Range(2, 5);
+ Range r2 = new Range(4, 9);
+ Range r3 = new Range(7, 12);
+
+ List<Range> mergedRanges = PositionUtil.mergeRanges(Arrays.asList(r1, r2, r3));
+ assertEquals(1, mergedRanges.size());
+ assertEquals(new Range(2, 12), mergedRanges.get(0));
+ }
+
+ @Test
+ public void shouldMergeNotContiguousRanges() {
+ Range r1 = new Range(2, 5);
+ Range r2 = new Range(4, 9);
+ Range r3 = new Range(10, 12);
+ Range r4 = new Range(12, 15);
+
+ List<Range> mergedRanges = PositionUtil.mergeRanges(Arrays.asList(r1, r2, r3, r4));
+ assertEquals(2, mergedRanges.size());
+ assertEquals(new Range(2, 9), mergedRanges.get(0));
+ assertEquals(new Range(10, 15), mergedRanges.get(1));
+ }
}
diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/coordinate/PositionUtil.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/coordinate/PositionUtil.java
index fbf4313..731de16 100644
--- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/coordinate/PositionUtil.java
+++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/coordinate/PositionUtil.java
@@ -16,9 +16,11 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.TreeSet;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;
@@ -170,6 +172,73 @@
}
/**
+ * Join a set of ranges if they describe a consecutive range when combined.
+ *
+ * @param ranges
+ * Collection of {@link Range}s that should be joined.
+ * @return The joined {@link Range} or <code>null</code> if {@link Range}s
+ * do not describe a consecutive {@link Range} when combined.
+ *
+ * @since 1.6
+ */
+ public static Range joinConsecutiveRanges(Collection<Range> ranges) {
+ if (ranges == null || ranges.isEmpty()) {
+ return null;
+ }
+
+ // put to list
+ List<Range> sortedRanges = new ArrayList<Range>(ranges);
+
+ // sort by 1) start, 2) end position
+ Collections.sort(sortedRanges, new Comparator<Range>() {
+
+ @Override
+ public int compare(Range o1, Range o2) {
+ if (o1.start == o2.start) {
+ return Integer.compare(o1.end, o2.end);
+ } else {
+ return Integer.compare(o1.start, o2.start);
+ }
+ }
+ });
+
+ int start = sortedRanges.get(0).start;
+ int end = sortedRanges.get(0).end;
+ for (int i = 1; i < sortedRanges.size(); i++) {
+ Range range = sortedRanges.get(i);
+ if (range.start > end) {
+ return null;
+ }
+ end = Integer.max(end, range.end);
+ }
+
+ return new Range(start, end);
+ }
+
+ /**
+ * Takes a collection of {@link Range}s and merges them to get
+ * {@link Range}s without overlapping. If there are no gaps between the
+ * {@link Range}s a single Range will be the result, otherwise multiple
+ * {@link Range}s will be in the resulting collection.
+ *
+ * @param ranges
+ * The {@link Range}s to merge.
+ * @return Collection of {@link Range}s without overlapping.
+ *
+ * @since 1.6
+ */
+ public static List<Range> mergeRanges(Collection<Range> ranges) {
+ Set<Integer> numbers = new TreeSet<Integer>();
+ for (Range range : ranges) {
+ for (int number = range.start; number < range.end; number++) {
+ numbers.add(number);
+ }
+ }
+
+ return getRanges(numbers);
+ }
+
+ /**
* Calculates the horizontal move direction based on the from and to column
* position.
*
diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectionUtils.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectionUtils.java
index db0a43e..b6f9c27 100644
--- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectionUtils.java
+++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/selection/SelectionUtils.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2012, 2018 Original authors and others.
+ * Copyright (c) 2012, 2019 Original authors and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -13,13 +13,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
-import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
+import org.eclipse.nebula.widgets.nattable.coordinate.PositionUtil;
import org.eclipse.nebula.widgets.nattable.coordinate.Range;
import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
@@ -110,50 +107,35 @@
* @since 1.4
*/
public static boolean hasConsecutiveSelection(SelectionLayer selectionLayer) {
- if (selectionLayer != null
- && SelectionUtils.isConsecutive(selectionLayer.getSelectedColumnPositions())) {
+ if (selectionLayer == null) {
+ return false;
+ }
- Map<Integer, Set<Integer>> positions = new HashMap<Integer, Set<Integer>>();
- int column = -1;
- int row = -1;
+ int[] selectedColumnPositions = selectionLayer.getSelectedColumnPositions();
+ if (selectedColumnPositions.length == 0) {
+ return false;
+ }
- // collect the selection information
- for (PositionCoordinate coord : selectionLayer.getSelectedCellPositions()) {
- Set<Integer> rows = positions.get(coord.columnPosition);
- if (rows == null) {
- rows = new LinkedHashSet<Integer>();
- positions.put(coord.columnPosition, rows);
- }
- rows.add(coord.rowPosition);
+ Arrays.sort(selectedColumnPositions);
- column = Math.max(column, coord.columnPosition);
- row = Math.max(row, coord.rowPosition);
+ Set<Range> selectedRowPositions = selectionLayer.getSelectedRowPositions();
+ Range range = PositionUtil.joinConsecutiveRanges(selectedRowPositions);
+ if (range == null) {
+ return false;
+ }
+
+ for (int colPosIdx = 0; colPosIdx < selectedColumnPositions.length; colPosIdx++) {
+ if (colPosIdx > 0 && selectedColumnPositions[colPosIdx - 1] + 1 != selectedColumnPositions[colPosIdx]) {
+ return false;
}
-
- // check if the selected region is a rectangle
- // every row collection for each column needs to have the same size
- // and the same content
- Set<Integer> previous = null;
- for (Set<Integer> rows : positions.values()) {
- if (previous != null && !previous.equals(rows)) {
+ final int columnPosition = selectedColumnPositions[colPosIdx];
+ for (int rowPositionIndex = range.start; rowPositionIndex < range.end; rowPositionIndex++) {
+ if (!selectionLayer.isCellPositionSelected(columnPosition, rowPositionIndex)) {
return false;
}
- previous = rows;
- }
-
- // test if rows are consecutive
- if (previous != null) {
- int[] rowPositions = new int[previous.size()];
- int i = 0;
- for (Integer rowPos : previous) {
- rowPositions[i++] = rowPos;
- }
- if (SelectionUtils.isConsecutive(rowPositions)) {
- return true;
- }
}
}
- return false;
+ return true;
}
/**
@@ -170,16 +152,34 @@
* @since 1.4
*/
public static ILayerCell getBottomRightCellInSelection(SelectionLayer selectionLayer) {
- if (hasConsecutiveSelection(selectionLayer)) {
- int column = -1;
- int row = -1;
- for (PositionCoordinate coord : selectionLayer.getSelectedCellPositions()) {
- column = Math.max(column, coord.columnPosition);
- row = Math.max(row, coord.rowPosition);
- }
- return selectionLayer.getCellByPosition(column, row);
+ if (selectionLayer == null) {
+ return null;
}
- return null;
+
+ int[] selectedColumnPositions = selectionLayer.getSelectedColumnPositions();
+ Arrays.sort(selectedColumnPositions);
+
+ Set<Range> selectedRowPositions = selectionLayer.getSelectedRowPositions();
+ Range range = PositionUtil.joinConsecutiveRanges(selectedRowPositions);
+ if (range == null) {
+ return null;
+ }
+
+ for (int colPosIdx = 0; colPosIdx < selectedColumnPositions.length; colPosIdx++) {
+ if (colPosIdx > 0 && selectedColumnPositions[colPosIdx - 1] + 1 != selectedColumnPositions[colPosIdx]) {
+ return null;
+ }
+ final int columnPosition = selectedColumnPositions[colPosIdx];
+ for (int rowPositionIndex = range.start; rowPositionIndex < range.end; rowPositionIndex++) {
+ if (!selectionLayer.isCellPositionSelected(columnPosition, rowPositionIndex)) {
+ return null;
+ }
+ }
+ }
+
+ int colPosition = selectedColumnPositions[selectedColumnPositions.length - 1];
+ int rowPosition = range.end - 1;
+ return selectionLayer.getCellByPosition(colPosition, rowPosition);
}
/**