diff --git a/exonum-java-binding/CHANGELOG.md b/exonum-java-binding/CHANGELOG.md index 4b3b95638d..887a5e8462 100644 --- a/exonum-java-binding/CHANGELOG.md +++ b/exonum-java-binding/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. `ProofMapIndexProxy#getIndexHash` accordingly. ### Added +- `stream` for sets: `KeySetIndex` and `ValueSetIndex`. (#1088) - Proofs of absence of an element with the specified index for `ProofListIndexProxy`. (#1081) ## [0.7.0] - 2019-07-17 diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/KeySetIndexProxy.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/KeySetIndexProxy.java index 44ac047b9d..ea88b3ae7e 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/KeySetIndexProxy.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/KeySetIndexProxy.java @@ -28,7 +28,11 @@ import com.exonum.binding.core.util.LibraryLoader; import com.google.protobuf.MessageLite; import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.LongSupplier; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * A key set is an index that contains no duplicate elements (keys). @@ -61,6 +65,11 @@ public final class KeySetIndexProxy extends AbstractIndexProxy implements Ite LibraryLoader.load(); } + // Note that we do *not* specify Spliterator.DISTINCT because it is documented in terms + // of Object#equals which this set does not use. + private static final int BASE_SPLITERATOR_CHARACTERISTICS = + Spliterator.NONNULL | Spliterator.ORDERED; + private final CheckingSerializerDecorator serializer; /** @@ -220,6 +229,25 @@ public Iterator iterator() { serializer::fromBytes); } + /** + * Returns a stream of the set elements. The elements are ordered lexicographically. + * + * @throws IllegalStateException if this set is not valid + */ + public Stream stream() { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(iterator(), streamCharacteristics()), + false); + } + + private int streamCharacteristics() { + if (dbView.canModify()) { + return BASE_SPLITERATOR_CHARACTERISTICS; + } else { + return BASE_SPLITERATOR_CHARACTERISTICS | Spliterator.IMMUTABLE; + } + } + /** * Removes the element from this set. If it's not in the set, does nothing. * diff --git a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/ValueSetIndexProxy.java b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/ValueSetIndexProxy.java index ffc4042500..c889cccbb4 100644 --- a/exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/ValueSetIndexProxy.java +++ b/exonum-java-binding/core/src/main/java/com/exonum/binding/core/storage/indices/ValueSetIndexProxy.java @@ -33,7 +33,11 @@ import com.google.common.annotations.VisibleForTesting; import com.google.protobuf.MessageLite; import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.LongSupplier; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import javax.annotation.Nullable; /** @@ -68,6 +72,11 @@ public final class ValueSetIndexProxy extends AbstractIndexProxy LibraryLoader.load(); } + // Note that we do *not* specify Spliterator.DISTINCT because it is documented in terms + // of Object#equals which this set does not use. + private static final int BASE_SPLITERATOR_CHARACTERISTICS = + Spliterator.NONNULL | Spliterator.ORDERED; + private final CheckingSerializerDecorator serializer; /** @@ -241,7 +250,7 @@ public Iterator hashes() { /** * Returns an iterator over the entries of this set. An entry is a hash-value pair. - * The entries are ordered by keys lexicographically. + * The entries are ordered by hashes lexicographically. * * @return an iterator over the entries of this set * @throws IllegalStateException if this set is not valid @@ -263,6 +272,27 @@ public Iterator> iterator() { private native void nativeIteratorFree(long iterNativeHandle); + /** + * Returns a stream of the entries in this set. An entry is a hash-value pair. + * The entries are ordered by hashes lexicographically. + * + * @throws IllegalStateException if this set is not valid + */ + public Stream> stream() { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(iterator(), streamCharacteristics()), + false + ); + } + + private int streamCharacteristics() { + if (dbView.canModify()) { + return BASE_SPLITERATOR_CHARACTERISTICS; + } else { + return BASE_SPLITERATOR_CHARACTERISTICS | Spliterator.IMMUTABLE; + } + } + /** * An entry of a value set index: a hash-value pair. * diff --git a/exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/KeySetIndexProxyIntegrationTest.java b/exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/KeySetIndexProxyIntegrationTest.java index 5b328f111f..c8f346581b 100644 --- a/exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/KeySetIndexProxyIntegrationTest.java +++ b/exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/KeySetIndexProxyIntegrationTest.java @@ -19,6 +19,7 @@ import static com.exonum.binding.core.storage.indices.TestStorageItems.K1; import static com.exonum.binding.core.storage.indices.TestStorageItems.K9; import static com.exonum.binding.core.storage.indices.TestStorageItems.V1; +import static java.util.stream.Collectors.toList; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -112,6 +113,22 @@ void testIterator() { }); } + @Test + void testStream() { + runTestWithView(database::createFork, (set) -> { + List elements = TestStorageItems.keys; + + elements.forEach(set::add); + + List streamElements = set.stream() + .collect(toList()); + + // Check that the stream includes all the elements added + // and that they appear in lexicographical order (the order of TestStorageItems.keys). + assertThat(streamElements, equalTo(elements)); + }); + } + @Test void removesAddedElement() { runTestWithView(database::createFork, (set) -> { diff --git a/exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/ValueSetIndexProxyIntegrationTest.java b/exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/ValueSetIndexProxyIntegrationTest.java index 08425da99c..7468442d5d 100644 --- a/exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/ValueSetIndexProxyIntegrationTest.java +++ b/exonum-java-binding/core/src/test/java/com/exonum/binding/core/storage/indices/ValueSetIndexProxyIntegrationTest.java @@ -19,6 +19,7 @@ import static com.exonum.binding.core.storage.indices.TestStorageItems.V1; import static com.exonum.binding.core.storage.indices.TestStorageItems.V2; import static com.exonum.binding.core.storage.indices.TestStorageItems.V9; +import static java.util.stream.Collectors.toList; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -37,7 +38,6 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; import org.junit.jupiter.api.Test; class ValueSetIndexProxyIntegrationTest @@ -159,10 +159,25 @@ void testIterator() { }); } + @Test + void testStream() { + runTestWithView(database::createFork, (set) -> { + List elements = TestStorageItems.values; + + elements.forEach(set::add); + + List> entriesFromStream = set.stream() + .collect(toList()); + List> entriesExpected = getOrderedEntries(elements); + + assertThat(entriesFromStream, equalTo(entriesExpected)); + }); + } + private static List getOrderedHashes(List elements) { return getOrderedEntries(elements).stream() .map(ValueSetIndexProxy.Entry::getHash) - .collect(Collectors.toList()); + .collect(toList()); } private static List> getOrderedEntries(List elements) { @@ -170,7 +185,7 @@ private static List> getOrderedEntries(List ValueSetIndexProxy.Entry.from(getHashOf(value), value)) .sorted((e1, e2) -> UnsignedBytes.lexicographicalComparator() .compare(e1.getHash().asBytes(), e2.getHash().asBytes())) - .collect(Collectors.toList()); + .collect(toList()); } @Test