Skip to content

Commit 82bcc37

Browse files
Add truncate and pop to ProofListIndexProxy [ECR-3669] (#1272)
Extract these operations to the interface. Move their tests to the base test of interface operations, make some of them parameterized.
1 parent 58234fe commit 82bcc37

9 files changed

Lines changed: 208 additions & 175 deletions

File tree

exonum-java-binding/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ the [migration guide](https://github.com/exonum/exonum-java-binding/blob/ejb/v0.
4242
schemas with unique namespaces. (#1181)
4343
- Implement `run-dev` command support for running the node in development mode. (#1217)
4444
- `Configurable` interface corresponding to `exonum.Configure`. (#1234)
45+
- `ProofMapIndexProxy#truncate` and `#removeLast`. (#1272)
4546
- Java 13 support.
4647

4748
### Changed

exonum-java-binding/core/rust/src/storage/proof_list_index.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,46 @@ pub extern "system" fn Java_com_exonum_binding_core_storage_indices_ProofListInd
137137
utils::unwrap_exc_or(&env, res, ptr::null_mut())
138138
}
139139

140+
/// Removes the last element from a list and returns it, or null pointer if it is empty.
141+
#[no_mangle]
142+
pub extern "system" fn Java_com_exonum_binding_core_storage_indices_ProofListIndexProxy_nativeRemoveLast(
143+
env: JNIEnv,
144+
_: JObject,
145+
list_handle: Handle,
146+
) -> jbyteArray {
147+
let res = panic::catch_unwind(|| {
148+
let val = match *handle::cast_handle::<IndexType>(list_handle) {
149+
IndexType::SnapshotIndex(_) => panic!("Unable to modify snapshot."),
150+
IndexType::ForkIndex(ref mut list) => list.pop(),
151+
};
152+
match val {
153+
Some(val) => env.byte_array_from_slice(&val),
154+
None => Ok(ptr::null_mut()),
155+
}
156+
});
157+
utils::unwrap_exc_or(&env, res, ptr::null_mut())
158+
}
159+
160+
/// Shortens the list, keeping the first len elements and dropping the rest.
161+
#[no_mangle]
162+
pub extern "system" fn Java_com_exonum_binding_core_storage_indices_ProofListIndexProxy_nativeTruncate(
163+
env: JNIEnv,
164+
_: JObject,
165+
list_handle: Handle,
166+
len: jlong,
167+
) {
168+
let res = panic::catch_unwind(|| match *handle::cast_handle::<IndexType>(list_handle) {
169+
IndexType::SnapshotIndex(_) => {
170+
panic!("Unable to modify snapshot.");
171+
}
172+
IndexType::ForkIndex(ref mut list) => {
173+
list.truncate(len as u64);
174+
Ok(())
175+
}
176+
});
177+
utils::unwrap_exc_or_default(&env, res)
178+
}
179+
140180
/// Returns `true` if the list is empty.
141181
#[no_mangle]
142182
pub extern "system" fn Java_com_exonum_binding_core_storage_indices_ProofListIndexProxy_nativeIsEmpty(

exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/AbstractListIndexProxy.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.exonum.binding.core.storage.indices.StoragePreconditions.checkElementIndex;
2020
import static com.exonum.binding.core.storage.indices.StoragePreconditions.checkNoNulls;
21+
import static com.google.common.base.Preconditions.checkArgument;
2122

2223
import com.exonum.binding.common.serialization.CheckingSerializerDecorator;
2324
import com.exonum.binding.core.proxy.NativeHandle;
@@ -91,6 +92,23 @@ public final T getLast() {
9192
return serializer.fromBytes(e);
9293
}
9394

95+
@Override
96+
public T removeLast() {
97+
notifyModified();
98+
byte[] e = nativeRemoveLast(getNativeHandle());
99+
if (e == null) {
100+
throw new NoSuchElementException("List is empty");
101+
}
102+
return serializer.fromBytes(e);
103+
}
104+
105+
@Override
106+
public void truncate(long newSize) {
107+
checkArgument(newSize >= 0, "New size must be non-negative: %s", newSize);
108+
notifyModified();
109+
nativeTruncate(getNativeHandle(), newSize);
110+
}
111+
94112
@Override
95113
public final void clear() {
96114
notifyModified();
@@ -133,6 +151,10 @@ public Stream<T> stream() {
133151

134152
abstract byte[] nativeGetLast(long nativeHandle);
135153

154+
abstract byte[] nativeRemoveLast(long nativeHandle);
155+
156+
abstract void nativeTruncate(long nativeHandle, long newSize);
157+
136158
abstract void nativeClear(long nativeHandle);
137159

138160
abstract boolean nativeIsEmpty(long nativeHandle);

exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/ListIndex.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,29 @@ public interface ListIndex<T> extends StorageIndex, Iterable<T> {
8989
*/
9090
T getLast();
9191

92+
/**
93+
* Removes the last element of the list and returns it.
94+
*
95+
* @return the last element of the list.
96+
* @throws NoSuchElementException if the list is empty
97+
* @throws IllegalStateException if this list is not valid
98+
* @throws UnsupportedOperationException if this list is read-only
99+
*/
100+
T removeLast();
101+
102+
/**
103+
* Truncates the list, reducing its size to {@code newSize}.
104+
*
105+
* <p>If {@code newSize < size()}, keeps the first {@code newSize} elements, removing the rest.
106+
* If {@code newSize >= size()}, has no effect.
107+
*
108+
* @param newSize the maximum number of elements to keep
109+
* @throws IllegalArgumentException if the new size is negative
110+
* @throws IllegalStateException if this list is not valid
111+
* @throws UnsupportedOperationException if this list is read-only
112+
*/
113+
void truncate(long newSize);
114+
92115
/**
93116
* Clears the list.
94117
*

exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/ListIndexProxy.java

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package com.exonum.binding.core.storage.indices;
1818

1919
import static com.exonum.binding.core.storage.indices.StoragePreconditions.checkIndexType;
20-
import static com.google.common.base.Preconditions.checkArgument;
2120

2221
import com.exonum.binding.common.serialization.CheckingSerializerDecorator;
2322
import com.exonum.binding.common.serialization.Serializer;
@@ -28,7 +27,6 @@
2827
import com.exonum.binding.core.storage.database.View;
2928
import com.exonum.binding.core.util.LibraryLoader;
3029
import com.google.protobuf.MessageLite;
31-
import java.util.NoSuchElementException;
3230
import java.util.function.LongSupplier;
3331

3432
/**
@@ -161,40 +159,6 @@ private ListIndexProxy(NativeHandle nativeHandle, IndexAddress address, View vie
161159
super(nativeHandle, address, view, serializer);
162160
}
163161

164-
/**
165-
* Removes the last element of the list and returns it.
166-
*
167-
* @return the last element of the list.
168-
* @throws NoSuchElementException if the list is empty
169-
* @throws IllegalStateException if this list is not valid
170-
* @throws UnsupportedOperationException if this list is read-only
171-
*/
172-
public E removeLast() {
173-
notifyModified();
174-
byte[] e = nativeRemoveLast(getNativeHandle());
175-
if (e == null) {
176-
throw new NoSuchElementException("List is empty");
177-
}
178-
return serializer.fromBytes(e);
179-
}
180-
181-
/**
182-
* Truncates the list, reducing its size to {@code newSize}.
183-
*
184-
* <p>If {@code newSize < size()}, keeps the first {@code newSize} elements, removing the rest.
185-
* If {@code newSize >= size()}, has no effect.
186-
*
187-
* @param newSize the maximum number of elements to keep
188-
* @throws IllegalArgumentException if the new size is negative
189-
* @throws IllegalStateException if this list is not valid
190-
* @throws UnsupportedOperationException if this list is read-only
191-
*/
192-
public void truncate(long newSize) {
193-
checkArgument(newSize >= 0, "New size must be non-negative: %s", newSize);
194-
notifyModified();
195-
nativeTruncate(getNativeHandle(), newSize);
196-
}
197-
198162
private static native long nativeCreate(String listName, long viewNativeHandle);
199163

200164
private static native long nativeCreateInGroup(String groupName, byte[] listId,
@@ -214,8 +178,10 @@ private static native long nativeCreateInGroup(String groupName, byte[] listId,
214178
@Override
215179
native byte[] nativeGetLast(long nativeHandle);
216180

181+
@Override
217182
native byte[] nativeRemoveLast(long nativeHandle);
218183

184+
@Override
219185
native void nativeTruncate(long nativeHandle, long newSize);
220186

221187
@Override

exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/ProofListIndexProxy.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,12 @@ public HashCode getIndexHash() {
234234
@Override
235235
native byte[] nativeGetLast(long nativeHandle);
236236

237+
@Override
238+
native byte[] nativeRemoveLast(long nativeHandle);
239+
240+
@Override
241+
native void nativeTruncate(long nativeHandle, long newSize);
242+
237243
@Override
238244
native void nativeClear(long nativeHandle);
239245

exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/BaseListIndexIntegrationTestable.java

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import java.util.function.Function;
4545
import java.util.stream.Stream;
4646
import org.junit.jupiter.api.Test;
47+
import org.junit.jupiter.params.ParameterizedTest;
48+
import org.junit.jupiter.params.provider.ValueSource;
4749

4850
/**
4951
* Base class for common ListIndex tests.
@@ -233,6 +235,114 @@ void getLastTwoElementList() {
233235
});
234236
}
235237

238+
@Test
239+
void removeLastEmptyList() {
240+
runTestWithView(database::createFork, (l) -> {
241+
assertThrows(NoSuchElementException.class, l::removeLast);
242+
});
243+
}
244+
245+
@Test
246+
void removeLastWithSnapshot() {
247+
runTestWithView(database::createSnapshot, (l) -> {
248+
assertThrows(UnsupportedOperationException.class, l::removeLast);
249+
});
250+
}
251+
252+
@Test
253+
void removeLastSingleElementList() {
254+
runTestWithView(database::createFork, (l) -> {
255+
String addedElement = V1;
256+
l.add(addedElement);
257+
String last = l.removeLast();
258+
259+
assertThat(last, equalTo(addedElement));
260+
assertTrue(l.isEmpty());
261+
});
262+
}
263+
264+
@Test
265+
void removeLastTwoElementList() {
266+
runTestWithView(database::createFork, (l) -> {
267+
l.add(V1);
268+
l.add(V2);
269+
String last = l.removeLast();
270+
271+
assertThat(last, equalTo(V2));
272+
assertThat(l.size(), equalTo(1L));
273+
assertThat(l.get(0), equalTo(V1));
274+
});
275+
}
276+
277+
@Test
278+
void truncateNonEmptyToZero() {
279+
runTestWithView(database::createFork, (l) -> {
280+
l.add(V1);
281+
l.truncate(0);
282+
283+
assertTrue(l.isEmpty());
284+
assertThat(l.size(), equalTo(0L));
285+
});
286+
}
287+
288+
@Test
289+
void truncateToSameSize() {
290+
runTestWithView(database::createFork, (l) -> {
291+
l.add(V1);
292+
long newSize = l.size();
293+
294+
l.truncate(newSize);
295+
296+
assertThat(l.size(), equalTo(newSize));
297+
});
298+
}
299+
300+
@Test
301+
void truncateToSmallerSize() {
302+
runTestWithView(database::createFork, (l) -> {
303+
long newSize = 1;
304+
l.add(V1);
305+
l.add(V2);
306+
l.truncate(newSize);
307+
308+
assertThat(l.size(), equalTo(newSize));
309+
});
310+
}
311+
312+
@ParameterizedTest
313+
@ValueSource(longs = {3, 4, Long.MAX_VALUE})
314+
void truncateToGreaterSizeHasNoEffect(long newSize) {
315+
runTestWithView(database::createFork, (l) -> {
316+
// Create a list of two elements
317+
l.add(V1);
318+
l.add(V2);
319+
320+
long oldSize = l.size();
321+
l.truncate(newSize);
322+
323+
assertThat(l.size(), equalTo(oldSize));
324+
});
325+
}
326+
327+
@ParameterizedTest
328+
@ValueSource(longs = {-1, -2, Long.MIN_VALUE})
329+
void truncateToNegativeSizeThrows(long invalidSize) {
330+
runTestWithView(database::createFork, (l) -> {
331+
l.add(V1);
332+
333+
assertThrows(IllegalArgumentException.class,
334+
() -> l.truncate(invalidSize));
335+
});
336+
}
337+
338+
@Test
339+
void truncateWithSnapshot() {
340+
runTestWithView(database::createSnapshot, (l) -> {
341+
assertThrows(UnsupportedOperationException.class,
342+
() -> l.truncate(0L));
343+
});
344+
}
345+
236346
@Test
237347
void clearEmptyHasNoEffect() {
238348
runTestWithView(database::createFork, (l) -> {

0 commit comments

Comments
 (0)