mirror of
https://github.com/corda/corda.git
synced 2025-01-29 15:43:55 +00:00
Add NonEmptySet
Add a new set-like collection which is constrained to ensure it's never empty.
This commit is contained in:
parent
4065cb25c0
commit
4744740913
@ -31,6 +31,9 @@ dependencies {
|
||||
testCompile 'org.assertj:assertj-core:3.4.1'
|
||||
testCompile "commons-fileupload:commons-fileupload:1.3.1"
|
||||
|
||||
// Guava: Google test library (collections test suite)
|
||||
testCompile "com.google.guava:guava-testlib:19.0"
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
@ -0,0 +1,83 @@
|
||||
package com.r3corda.core.utilities
|
||||
|
||||
/**
|
||||
* A set which is constrained to ensure it can never be empty. An initial value must be provided at
|
||||
* construction, and attempting to remove the last element will cause an IllegalStateException.
|
||||
*/
|
||||
class NonEmptySet<T>(initial: T, private val set: MutableSet<T> = mutableSetOf()) : MutableSet<T> {
|
||||
init {
|
||||
require (set.isEmpty()) { "Provided set must be empty." }
|
||||
set.add(initial)
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = set.size
|
||||
|
||||
override fun add(element: T): Boolean = set.add(element)
|
||||
override fun addAll(elements: Collection<T>): Boolean = set.addAll(elements)
|
||||
override fun clear() = throw UnsupportedOperationException()
|
||||
override fun contains(element: T): Boolean = set.contains(element)
|
||||
override fun containsAll(elements: Collection<T>): Boolean = set.containsAll(elements)
|
||||
override fun isEmpty(): Boolean = false
|
||||
|
||||
override fun iterator(): MutableIterator<T> = Iterator<T>(set.iterator())
|
||||
|
||||
override fun remove(element: T): Boolean =
|
||||
// Test either there's more than one element, or the removal is a no-op
|
||||
if (size > 1)
|
||||
set.remove(element)
|
||||
else if (!contains(element))
|
||||
false
|
||||
else
|
||||
throw IllegalStateException()
|
||||
|
||||
override fun removeAll(elements: Collection<T>): Boolean =
|
||||
if (size > elements.size)
|
||||
set.removeAll(elements)
|
||||
else if (!containsAll(elements))
|
||||
// Remove the common elements
|
||||
set.removeAll(elements)
|
||||
else
|
||||
throw IllegalStateException()
|
||||
|
||||
override fun retainAll(elements: Collection<T>): Boolean {
|
||||
val iterator = iterator()
|
||||
val ret = false
|
||||
|
||||
// The iterator will throw an IllegalStateException if we try removing the last element
|
||||
while (iterator.hasNext()) {
|
||||
if (!elements.contains(iterator.next())) {
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
if (other is Set<*>)
|
||||
// Delegate down to the wrapped set's equals() function
|
||||
set.equals(other)
|
||||
else
|
||||
false
|
||||
|
||||
override fun hashCode(): Int = set.hashCode()
|
||||
override fun toString(): String = set.toString()
|
||||
|
||||
inner class Iterator<T>(val iterator: MutableIterator<T>) : MutableIterator<T> {
|
||||
override fun hasNext(): Boolean = iterator.hasNext()
|
||||
override fun next(): T = iterator.next()
|
||||
override fun remove() =
|
||||
if (set.size > 1)
|
||||
iterator.remove()
|
||||
else
|
||||
throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> nonEmptySetOf(initial: T, vararg elements: T): NonEmptySet<T> {
|
||||
val set = NonEmptySet<T>(initial)
|
||||
// We add the first element twice, but it's a set, so who cares
|
||||
set.addAll(elements)
|
||||
return set
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package com.r3corda.core.utilities
|
||||
|
||||
import com.google.common.collect.testing.SetTestSuiteBuilder
|
||||
import com.google.common.collect.testing.TestIntegerSetGenerator
|
||||
import com.google.common.collect.testing.features.CollectionFeature
|
||||
import com.google.common.collect.testing.features.CollectionSize
|
||||
import com.google.common.collect.testing.testers.CollectionAddAllTester
|
||||
import com.google.common.collect.testing.testers.CollectionClearTester
|
||||
import com.google.common.collect.testing.testers.CollectionRemoveAllTester
|
||||
import com.google.common.collect.testing.testers.CollectionRetainAllTester
|
||||
import junit.framework.TestSuite
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Suite
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@RunWith(Suite::class)
|
||||
@Suite.SuiteClasses(
|
||||
NonEmptySetTest.Guava::class,
|
||||
NonEmptySetTest.Remove::class
|
||||
)
|
||||
class NonEmptySetTest {
|
||||
/**
|
||||
* Guava test suite generator for NonEmptySet.
|
||||
*/
|
||||
class Guava {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun suite(): TestSuite
|
||||
= SetTestSuiteBuilder
|
||||
.using(NonEmptySetGenerator())
|
||||
.named("test NonEmptySet with several values")
|
||||
.withFeatures(
|
||||
CollectionSize.SEVERAL,
|
||||
CollectionFeature.ALLOWS_NULL_VALUES,
|
||||
CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
|
||||
CollectionFeature.GENERAL_PURPOSE
|
||||
)
|
||||
// Kotlin throws the wrong exception in this cases
|
||||
.suppressing(CollectionAddAllTester::class.java.getMethod("testAddAll_nullCollectionReference"))
|
||||
// Disable tests that try to remove everything:
|
||||
.suppressing(CollectionRemoveAllTester::class.java.getMethod("testRemoveAll_nullCollectionReferenceNonEmptySubject"))
|
||||
.suppressing(CollectionClearTester::class.java.getMethods().toList())
|
||||
.suppressing(CollectionRetainAllTester::class.java.getMethods().toList())
|
||||
.createTestSuite()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test removal, which Guava's standard tests can't cover for us.
|
||||
*/
|
||||
class Remove {
|
||||
@Test
|
||||
fun `construction`() {
|
||||
val expected = 17
|
||||
val basicSet = nonEmptySetOf(expected)
|
||||
val actual = basicSet.first()
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun `remove sole element`() {
|
||||
val basicSet = nonEmptySetOf(-17)
|
||||
basicSet.remove(-17)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remove one of two elements`() {
|
||||
val basicSet = nonEmptySetOf(-17, 17)
|
||||
basicSet.remove(-17)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remove element which does not exist`() {
|
||||
val basicSet = nonEmptySetOf(-17)
|
||||
basicSet.remove(-5)
|
||||
assertEquals(1, basicSet.size)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun `remove via iterator`() {
|
||||
val basicSet = nonEmptySetOf(-17, 17)
|
||||
val iterator = basicSet.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generator of non empty set instances needed for testing.
|
||||
*/
|
||||
class NonEmptySetGenerator : TestIntegerSetGenerator() {
|
||||
override fun create(elements: Array<out Int?>?): NonEmptySet<Int?>? {
|
||||
val set = nonEmptySetOf(elements!!.first())
|
||||
set.addAll(elements.toList())
|
||||
return set
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user