Skip to content

Commit cac1c21

Browse files
committed
Polishing Window scrolling API.
Moved general-purpose factory methods to the common ScrollPosition interface. Introduced a couple of domain specific methods to conveniently work with both Keyset- (to e.g. change direction) and OffsetScrollPosition (to e.g. advance the offset). Consolidated unit tests accordingly. Moved WindowIterator to ….data.support package. Fixed case of Direction enum values. Fixes #2824.
1 parent ccd56ef commit cac1c21

14 files changed

+385
-212
lines changed

src/main/asciidoc/repositories-paging-sorting.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Page<User> findByLastname(String lastname, Pageable pageable);
1414
1515
Slice<User> findByLastname(String lastname, Pageable pageable);
1616
17-
Scroll<User> findTop10ByLastname(String lastname, ScrollPosition position, Sort sort);
17+
Window<User> findTop10ByLastname(String lastname, ScrollPosition position, Sort sort);
1818
1919
List<User> findByLastname(String lastname, Sort sort);
2020

src/main/java/org/springframework/data/domain/KeysetScrollPosition.java

+81-59
Original file line numberDiff line numberDiff line change
@@ -18,72 +18,61 @@
1818
import java.util.Collections;
1919
import java.util.LinkedHashMap;
2020
import java.util.Map;
21+
import java.util.Objects;
2122

23+
import org.springframework.lang.Nullable;
2224
import org.springframework.util.Assert;
2325
import org.springframework.util.ObjectUtils;
2426

2527
/**
26-
* A {@link ScrollPosition} based on the last seen keyset. Keyset scrolling must be associated with a {@link Sort
27-
* well-defined sort} to be able to extract the keyset when resuming scrolling within the sorted result set.
28+
* A {@link ScrollPosition} based on the last seen key set. Keyset scrolling must be associated with a {@link Sort
29+
* well-defined sort} to be able to extract the key set when resuming scrolling within the sorted result set.
2830
*
2931
* @author Mark Paluch
32+
* @author Oliver Drotbohm
3033
* @since 3.1
3134
*/
3235
public final class KeysetScrollPosition implements ScrollPosition {
3336

34-
private static final KeysetScrollPosition initial = new KeysetScrollPosition(Collections.emptyMap(),
35-
Direction.Forward);
37+
private static final KeysetScrollPosition INITIAL = new KeysetScrollPosition(Collections.emptyMap(),
38+
Direction.FORWARD);
3639

3740
private final Map<String, Object> keys;
38-
3941
private final Direction direction;
4042

4143
private KeysetScrollPosition(Map<String, Object> keys, Direction direction) {
44+
45+
Assert.notNull(keys, "Keys must not be null");
46+
Assert.notNull(direction, "Direction must not be null");
47+
4248
this.keys = keys;
4349
this.direction = direction;
4450
}
4551

4652
/**
4753
* Creates a new initial {@link KeysetScrollPosition} to start scrolling using keyset-queries.
4854
*
49-
* @return a new initial {@link KeysetScrollPosition} to start scrolling using keyset-queries.
50-
*/
51-
public static KeysetScrollPosition initial() {
52-
return initial;
53-
}
54-
55-
/**
56-
* Creates a new {@link KeysetScrollPosition} from a keyset.
57-
*
58-
* @param keys must not be {@literal null}.
59-
* @return a new {@link KeysetScrollPosition} for the given keyset.
55+
* @return will never be {@literal null}.
6056
*/
61-
public static KeysetScrollPosition of(Map<String, ?> keys) {
62-
return of(keys, Direction.Forward);
57+
static KeysetScrollPosition initial() {
58+
return INITIAL;
6359
}
6460

6561
/**
66-
* Creates a new {@link KeysetScrollPosition} from a keyset and {@link Direction}.
62+
* Creates a new {@link KeysetScrollPosition} from a key set and {@link Direction}.
6763
*
6864
* @param keys must not be {@literal null}.
6965
* @param direction must not be {@literal null}.
70-
* @return a new {@link KeysetScrollPosition} for the given keyset and {@link Direction}.
66+
* @return will never be {@literal null}.
7167
*/
72-
public static KeysetScrollPosition of(Map<String, ?> keys, Direction direction) {
68+
static KeysetScrollPosition of(Map<String, ?> keys, Direction direction) {
7369

7470
Assert.notNull(keys, "Keys must not be null");
7571
Assert.notNull(direction, "Direction must not be null");
7672

77-
if (keys.isEmpty()) {
78-
return initial();
79-
}
80-
81-
return new KeysetScrollPosition(Collections.unmodifiableMap(new LinkedHashMap<>(keys)), direction);
82-
}
83-
84-
@Override
85-
public boolean isInitial() {
86-
return keys.isEmpty();
73+
return keys.isEmpty()
74+
? initial()
75+
: new KeysetScrollPosition(Collections.unmodifiableMap(new LinkedHashMap<>(keys)), direction);
8776
}
8877

8978
/**
@@ -100,45 +89,78 @@ public Direction getDirection() {
10089
return direction;
10190
}
10291

103-
@Override
104-
public boolean equals(Object o) {
105-
if (this == o)
106-
return true;
107-
if (o == null || getClass() != o.getClass())
108-
return false;
109-
KeysetScrollPosition that = (KeysetScrollPosition) o;
110-
return ObjectUtils.nullSafeEquals(keys, that.keys) && direction == that.direction;
92+
/**
93+
* Returns whether the current {@link KeysetScrollPosition} scrolls forward.
94+
*
95+
* @return whether the current {@link KeysetScrollPosition} scrolls forward.
96+
*/
97+
public boolean scrollsForward() {
98+
return direction == Direction.FORWARD;
11199
}
112100

113-
@Override
114-
public int hashCode() {
101+
/**
102+
* Returns whether the current {@link KeysetScrollPosition} scrolls backward.
103+
*
104+
* @return whether the current {@link KeysetScrollPosition} scrolls backward.
105+
*/
106+
public boolean scrollsBackward() {
107+
return direction == Direction.BACKWARD;
108+
}
115109

116-
int result = 17;
110+
/**
111+
* Returns a {@link KeysetScrollPosition} based on the same keyset and scrolling forward.
112+
*
113+
* @return will never be {@literal null}.
114+
*/
115+
public KeysetScrollPosition forward() {
116+
return direction == Direction.FORWARD ? this : new KeysetScrollPosition(keys, Direction.FORWARD);
117+
}
117118

118-
result += 31 * ObjectUtils.nullSafeHashCode(keys);
119-
result += 31 * ObjectUtils.nullSafeHashCode(direction);
119+
/**
120+
* Returns a {@link KeysetScrollPosition} based on the same keyset and scrolling backward.
121+
*
122+
* @return will never be {@literal null}.
123+
*/
124+
public KeysetScrollPosition backward() {
125+
return direction == Direction.BACKWARD ? this : new KeysetScrollPosition(keys, Direction.BACKWARD);
126+
}
120127

121-
return result;
128+
/**
129+
* Returns a new {@link KeysetScrollPosition} with the direction reversed.
130+
*
131+
* @return will never be {@literal null}.
132+
*/
133+
public KeysetScrollPosition reverse() {
134+
return new KeysetScrollPosition(keys, direction.reverse());
122135
}
123136

124137
@Override
125-
public String toString() {
126-
return String.format("KeysetScrollPosition [%s, %s]", direction, keys);
138+
public boolean isInitial() {
139+
return keys.isEmpty();
127140
}
128141

129-
/**
130-
* Keyset scrolling direction.
131-
*/
132-
public enum Direction {
142+
@Override
143+
public boolean equals(@Nullable Object o) {
144+
145+
if (this == o) {
146+
return true;
147+
}
133148

134-
/**
135-
* Forward (default) direction to scroll from the beginning of the results to their end.
136-
*/
137-
Forward,
149+
if (!(o instanceof KeysetScrollPosition that)) {
150+
return false;
151+
}
152+
153+
return ObjectUtils.nullSafeEquals(keys, that.keys) //
154+
&& direction == that.direction;
155+
}
138156

139-
/**
140-
* Backward direction to scroll from the end of the results to their beginning.
141-
*/
142-
Backward;
157+
@Override
158+
public int hashCode() {
159+
return Objects.hash(keys, direction);
160+
}
161+
162+
@Override
163+
public String toString() {
164+
return String.format("KeysetScrollPosition [%s, %s]", direction, keys);
143165
}
144166
}

src/main/java/org/springframework/data/domain/OffsetScrollPosition.java

+49-29
Original file line numberDiff line numberDiff line change
@@ -15,49 +15,54 @@
1515
*/
1616
package org.springframework.data.domain;
1717

18+
import java.util.Objects;
1819
import java.util.function.IntFunction;
1920

21+
import org.springframework.lang.Nullable;
2022
import org.springframework.util.Assert;
21-
import org.springframework.util.ObjectUtils;
2223

2324
/**
2425
* A {@link ScrollPosition} based on the offsets within query results.
2526
*
2627
* @author Mark Paluch
28+
* @author Oliver Drotbohm
2729
* @since 3.1
2830
*/
2931
public final class OffsetScrollPosition implements ScrollPosition {
3032

31-
private static final OffsetScrollPosition initial = new OffsetScrollPosition(0);
33+
private static final OffsetScrollPosition INITIAL = new OffsetScrollPosition(0);
3234

3335
private final long offset;
3436

37+
/**
38+
* Creates a new {@link OffsetScrollPosition} for the given non-negative offset.
39+
*
40+
* @param offset must be greater or equal to zero.
41+
*/
3542
private OffsetScrollPosition(long offset) {
43+
44+
Assert.isTrue(offset >= 0, "Offset must not be negative");
45+
3646
this.offset = offset;
3747
}
3848

3949
/**
4050
* Creates a new initial {@link OffsetScrollPosition} to start scrolling using offset/limit.
4151
*
42-
* @return a new initial {@link OffsetScrollPosition} to start scrolling using offset/limit.
52+
* @return will never be {@literal null}.
4353
*/
44-
public static OffsetScrollPosition initial() {
45-
return initial;
54+
static OffsetScrollPosition initial() {
55+
return INITIAL;
4656
}
4757

4858
/**
4959
* Creates a new {@link OffsetScrollPosition} from an {@code offset}.
5060
*
51-
* @param offset
52-
* @return a new {@link OffsetScrollPosition} with the given {@code offset}.
61+
* @param offset the non-negative offset to start at.
62+
* @return will never be {@literal null}.
5363
*/
54-
public static OffsetScrollPosition of(long offset) {
55-
56-
if (offset == 0) {
57-
return initial();
58-
}
59-
60-
return new OffsetScrollPosition(offset);
64+
static OffsetScrollPosition of(long offset) {
65+
return offset == 0 ? initial() : new OffsetScrollPosition(offset);
6166
}
6267

6368
/**
@@ -73,36 +78,51 @@ public static IntFunction<OffsetScrollPosition> positionFunction(long startOffse
7378
return startOffset == 0 ? OffsetPositionFunction.ZERO : new OffsetPositionFunction(startOffset);
7479
}
7580

76-
@Override
77-
public boolean isInitial() {
78-
return offset == 0;
79-
}
80-
8181
/**
82+
* The zero or positive offset.
83+
*
8284
* @return the offset.
8385
*/
8486
public long getOffset() {
8587
return offset;
8688
}
8789

90+
/**
91+
* Returns a new {@link OffsetScrollPosition} that has been advanced by the given value. Negative deltas will be
92+
* constrained so that the new offset is at least zero.
93+
*
94+
* @param delta the value to advance the current offset by.
95+
* @return will never be {@literal null}.
96+
*/
97+
public OffsetScrollPosition advanceBy(long delta) {
98+
99+
var value = offset + delta;
100+
101+
return new OffsetScrollPosition(value < 0 ? 0 : value);
102+
}
103+
104+
@Override
105+
public boolean isInitial() {
106+
return offset == 0;
107+
}
108+
88109
@Override
89-
public boolean equals(Object o) {
90-
if (this == o)
110+
public boolean equals(@Nullable Object o) {
111+
112+
if (this == o) {
91113
return true;
92-
if (o == null || getClass() != o.getClass())
114+
}
115+
116+
if (!(o instanceof OffsetScrollPosition that)) {
93117
return false;
94-
OffsetScrollPosition that = (OffsetScrollPosition) o;
118+
}
119+
95120
return offset == that.offset;
96121
}
97122

98123
@Override
99124
public int hashCode() {
100-
101-
int result = 17;
102-
103-
result += 31 * ObjectUtils.nullSafeHashCode(offset);
104-
105-
return result;
125+
return Objects.hash(offset);
106126
}
107127

108128
@Override

src/main/java/org/springframework/data/domain/Pageable.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ default OffsetScrollPosition toScrollPosition() {
174174
throw new IllegalStateException("Cannot create OffsetScrollPosition from an unpaged instance");
175175
}
176176

177-
return OffsetScrollPosition.of(getOffset());
177+
return ScrollPosition.offset(getOffset());
178178
}
179-
180179
}

0 commit comments

Comments
 (0)