diff --git a/kroki-collections/pom.xml b/kroki-collections/pom.xml new file mode 100644 index 0000000..dd17d65 --- /dev/null +++ b/kroki-collections/pom.xml @@ -0,0 +1,72 @@ + + + + kroki + io.github.kerubistan.kroki + 1.24-SNAPSHOT + + + 4.0.0 + + kroki-collections + jar + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + + + + + + org.jetbrains.kotlin + kotlin-stdlib + + + org.jetbrains.kotlin + kotlin-test-junit + + + org.junit.jupiter + junit-jupiter-api + + + org.junit.jupiter + junit-jupiter-engine + + + org.junit.jupiter + junit-jupiter + + + io.github.kerubistan.kroki + kroki-delegates + 1.24-SNAPSHOT + + + io.kotest + kotest-assertions-core-jvm + 5.9.1 + + + + + src/main/kotlin + src/test/kotlin + + + org.jetbrains.dokka + dokka-maven-plugin + + + maven-surefire-plugin + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + + + diff --git a/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/Collections.kt b/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/Collections.kt new file mode 100644 index 0000000..ba3d249 --- /dev/null +++ b/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/Collections.kt @@ -0,0 +1,29 @@ +package io.github.kerubistan.kroki.collections + +fun immutableListOf(vararg items: T): List = + when (items.size) { + 0 -> emptyList() + 1 -> listOf(items.single()) + else -> ImmutableArrayList(items) + } + +inline fun List.toImmutableList(): List = when { + this.isEmpty() -> emptyList() + this.size == 1 -> listOf(first()) + else -> immutableListOf(*(this.toTypedArray())) +} + +internal fun listHashCode(list: List): Int { + var hashCode = 1 + list.forEach { hashCode = (hashCode * 31) + it.hashCode() } + return hashCode +} + +fun > List.immutableSorted(): List = + when (this) { + is ImmutableArrayList -> this.sortedToImmutable() + else -> this.sorted() + } + +inline fun buildList(builder: ImmutableListBuilder.() -> Unit) = + ImmutableListBuilder().apply(builder).build() diff --git a/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/ImmutableArrayList.kt b/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/ImmutableArrayList.kt new file mode 100644 index 0000000..11097a7 --- /dev/null +++ b/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/ImmutableArrayList.kt @@ -0,0 +1,130 @@ +package io.github.kerubistan.kroki.collections + +import io.github.kerubistan.kroki.delegates.weak + +/** + * Implementation of an immutable list, internally based on an array. + */ +internal class ImmutableArrayList() : List { + + internal lateinit var items: Array + + internal constructor(array: Array) : this() { + this.items = array + } + + companion object { + fun of(vararg items: T): List = ImmutableArrayList(items) + } + + override val size: Int + get() = items.size + + override fun get(index: Int): T { + try { + return items[index] + } catch (aiob: ArrayIndexOutOfBoundsException) { + throw IllegalArgumentException("size is ${items.size}, requested index is $index", aiob) + } + } + + override fun isEmpty(): Boolean = size == 0 + + private class ImmutableArrayListIterator(private val items: Array) : ListIterator { + private var index = 0 + + constructor(items: Array, index: Int) : this(items) { + check(index > 0) { "Index must be greater than zero" } + check(index < items.size) { "Index must be less than the size of the array" } + this.index = index + } + + override fun hasNext(): Boolean = items.size > index + override fun hasPrevious(): Boolean = index > 0 + + override fun next(): T { + try { + val returnValue = items[index] + index += 1 + return returnValue + } catch (aie: ArrayIndexOutOfBoundsException) { + throw IllegalArgumentException("no more items left", aie) + } + } + + override fun nextIndex(): Int = index + 1 + + override fun previous(): T { + require(index > 0) { "No previous item before the start of the list" } + return items[--index] + } + + + override fun previousIndex(): Int = index - 1 + } + + override fun iterator(): Iterator = ImmutableArrayListIterator(this.items) + + override fun listIterator(): ListIterator = ImmutableArrayListIterator(this.items) + + override fun listIterator(index: Int): ListIterator = ImmutableArrayListIterator(this.items, index) + + override fun subList(fromIndex: Int, toIndex: Int): List = + when { + toIndex == fromIndex -> emptyList() + fromIndex == toIndex - 1 -> listOf(this.items[fromIndex]) + fromIndex == 0 && toIndex == size + 1 -> this + else -> ImmutableSubArrayList(fromIndex, toIndex, this.items) + } + + override fun lastIndexOf(element: T): Int = items.lastIndexOf(element) + + override fun indexOf(element: T): Int = items.indexOf(element) + + override fun containsAll(elements: Collection): Boolean = elements.all(items::contains) + + override fun contains(element: T): Boolean = items.contains(element) + + override fun equals(other: Any?): Boolean { + when (other) { + is ImmutableArrayList<*> -> return this.items.contentEquals(other.items) + is List<*> -> { + if (this.size != other.size) + return false + this.items.forEachIndexed { index, item -> + if (item != other[index]) { + return false + } + } + return true + } + + else -> + return false + } + } + + private val hashCode by lazy { listHashCode(this) } + + override fun hashCode(): Int = hashCode + + private val stringValue by weak { + buildString { + append('[') + this@ImmutableArrayList.items.forEachIndexed { index, it -> + if (index != 0) { + append(',') + } + append(it) + } + append(']') + } + } + + override fun toString(): String = stringValue + + internal fun sortedToImmutable(): List = + ImmutableArrayList( + this.items.clone().apply { sort() } + ) +} diff --git a/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/ImmutableListBuilder.kt b/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/ImmutableListBuilder.kt new file mode 100644 index 0000000..30a2454 --- /dev/null +++ b/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/ImmutableListBuilder.kt @@ -0,0 +1,49 @@ +package io.github.kerubistan.kroki.collections + +class ImmutableListBuilder { + private var increment : Int = 128 + private var size : Int = 0 + private var items : Array = arrayOfNulls(increment) + + fun add(item : T) { + if (size >= items.size) { + items = items.copyOf(size + increment) + } + items[size] = item + size ++ + } + + fun addAll(vararg items : T) { + if(size + items.size >= this.items.size) { + this.items = this.items.copyOf(size + maxOf(items.size, increment)) + } + items.copyInto(this.items, this.size) + size += items.size + } + + fun addAll(newItems : Iterable) { + when(newItems) { + is ImmutableArrayList -> { + if(size + newItems.items.size >= this.items.size) { + this.items = this.items.copyOf(size + maxOf(newItems.items.size, increment)) + } + newItems.items.copyInto(this.items, size) + size += newItems.items.size + } + else -> + // this is a bit slow this way, it should be avoided if possible + newItems.forEach { add(it) } + } + } + + fun build() : List = + when (size) { + 0 -> emptyList() + items.size -> { + ImmutableArrayList(items as Array) + } + else -> { + ImmutableArrayList((items.copyOf(size)) as Array) + } + } +} \ No newline at end of file diff --git a/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/ImmutableSubArrayList.kt b/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/ImmutableSubArrayList.kt new file mode 100644 index 0000000..dde7ddb --- /dev/null +++ b/kroki-collections/src/main/kotlin/io/github/kerubistan/kroki/collections/ImmutableSubArrayList.kt @@ -0,0 +1,104 @@ +package io.github.kerubistan.kroki.collections + +import io.github.kerubistan.kroki.delegates.weak + +/** + * Sub-list of an immutable array list. + * Since the arrayList is already immutable, the array can be passed over to the sub-list, and it + * will give faster access to elements. + */ +internal class ImmutableSubArrayList( + private val offset: Int, + private val limit: Int, + private val items: Array +) : List { + + init { + require(offset >= 0) { "offset ($offset) must be greater than 0" } + require(limit > offset) { "limit ($limit) must be bigger than the offset ($offset)" } + require(limit <= items.size) { "limit ($limit) must be less than or equal to the size of the array (${items.size})" } + } + + override val size: Int = limit - offset + + override fun get(index: Int): T { + require(index + offset < limit) { "index $index outside of boundaries" } + return items[offset + index] + } + + override fun isEmpty(): Boolean = false // because limit > offset, for empty list use emptyList + + internal class SubListIterator( + private val items: Array, + private val offset: Int, + private val limit: Int, + private var position: Int = offset + ) : ListIterator { + override fun hasNext(): Boolean = position < limit + override fun hasPrevious(): Boolean = position > offset + + override fun next(): T { + require(position < limit) { "reached end of the list" } + return items[position++] + } + + override fun nextIndex(): Int = position + 1 + + override fun previous(): T { + require(position > offset) + return items[position--] + } + + override fun previousIndex(): Int = position - 1 + } + + override fun iterator(): Iterator = SubListIterator(items, offset, limit) + + override fun listIterator(): ListIterator = SubListIterator(items, offset, limit) + + override fun listIterator(index: Int): ListIterator = SubListIterator(items, offset, limit, index) + + override fun subList(fromIndex: Int, toIndex: Int): List = + when { + toIndex == fromIndex -> emptyList() + toIndex < fromIndex -> throw IllegalArgumentException("toIndex ($toIndex) < fromIndex ($fromIndex)") + else -> ImmutableSubArrayList(offset + fromIndex, offset + toIndex, items) + } + + override fun lastIndexOf(element: T): Int = (offset until limit).last { this[it] == element } + + override fun indexOf(element: T): Int = (offset until limit).first { this[it] == element } + + override fun containsAll(elements: Collection): Boolean = elements.isEmpty() || elements.all { it in this } + + override fun contains(element: T): Boolean = (offset until limit).any { items[it] == element } + + private val stringValue by weak { + buildString { + append('[') + this@ImmutableSubArrayList.forEachIndexed { index, item -> + if (index > 0) { + append(',') + } + append(item) + } + append(']') + } + } + + override fun toString(): String { + return stringValue + } + + override fun equals(other: Any?): Boolean { + return other != null + && other is List<*> + && other.size == size + && (0 until this.lastIndex).all { index -> this[index] == other[index] } + } + + private val hashCode by lazy { listHashCode(this) } + + override fun hashCode() = hashCode + +} \ No newline at end of file diff --git a/kroki-collections/src/test/kotlin/io/github/kerubistan/kroki/collections/CollectionsKtTest.kt b/kroki-collections/src/test/kotlin/io/github/kerubistan/kroki/collections/CollectionsKtTest.kt new file mode 100644 index 0000000..eefdb8d --- /dev/null +++ b/kroki-collections/src/test/kotlin/io/github/kerubistan/kroki/collections/CollectionsKtTest.kt @@ -0,0 +1,81 @@ +package io.github.kerubistan.kroki.collections + +import io.kotest.matchers.collections.* +import io.kotest.matchers.should +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class CollectionsKtTest { + + @Test + fun testImmutableListOf() { + immutableListOf(1).shouldNotBeEmpty() + immutableListOf(1).shouldBeSingleton() + immutableListOf().shouldBeEmpty() + immutableListOf(1, 2, 3).shouldStartWith(1) + immutableListOf(1, 2, 3).shouldEndWith(3) + immutableListOf(1, 2, 3).shouldContainAll(1, 2) + immutableListOf(1, 2, 3).shouldContainAll(1, 2, 3) + immutableListOf(1, 2, 3).shouldNotContainAll(1, 2, 3, 4) + + assertEquals(listOf(), listOf().toImmutableList()) + assertEquals(listOf("A"), listOf("A").toImmutableList()) + assertEquals(listOf("A", "B"), listOf("A", "B").toImmutableList()) + } + + @Test + fun buildList() { + buildList { } shouldBe emptyList() + buildList { add("A") } shouldBe listOf("A") + buildList { + add("A") + add("B") + add("C") + } shouldBe listOf("A", "B", "C") + buildList { repeat(1000) { add(it.toString()) } }.let { + it shouldStartWith "0" + it shouldEndWith "999" + it shouldHaveSize 1000 + } + + buildList { + add("A") + addAll("B", "C") + add("D") + } shouldBe immutableListOf("A", "B", "C", "D") + + buildList { + repeat(128) { + addAll("A", "B", "C", "D", "E", "F", "G", "H") + } + }.let { + it shouldStartWith "A" + it shouldEndWith "H" + it shouldHaveSize 8 * 128 + } + + buildList { + val list = immutableListOf("A", "B", "C", "D", "E", "F", "G", "H") + repeat(128) { + addAll(list) + } + }.let { + it shouldStartWith "A" + it shouldEndWith "H" + it shouldHaveSize (8 * 128) + } + + buildList { + val list = listOf("A", "B", "C", "D", "E", "F", "G", "H") + repeat(128) { + addAll(list) + } + }.let { + it shouldStartWith "A" + it shouldEndWith "H" + it shouldHaveSize (8 * 128) + } + + } +} \ No newline at end of file diff --git a/kroki-collections/src/test/kotlin/io/github/kerubistan/kroki/collections/ImmutableArrayListTest.kt b/kroki-collections/src/test/kotlin/io/github/kerubistan/kroki/collections/ImmutableArrayListTest.kt new file mode 100644 index 0000000..234a7b1 --- /dev/null +++ b/kroki-collections/src/test/kotlin/io/github/kerubistan/kroki/collections/ImmutableArrayListTest.kt @@ -0,0 +1,140 @@ +package io.github.kerubistan.kroki.collections + +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.util.ArrayList +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class ImmutableArrayListTest { + + @Test + fun equals() { + assertEquals(immutableListOf("A", "B", "C"), immutableListOf("A", "B", "C")) + assertEquals(immutableListOf("A", "B", "C"), listOf("A", "B", "C")) + assertNotEquals(immutableListOf("A", "B", "C"), listOf("A", "B", "C", "D")) + assertNotEquals(immutableListOf("A", "B", "C", "D"), listOf("A", "B", "C")) + assertNotEquals(immutableListOf("A", "B", "C"), listOf("A", "D", "C")) + assertNotEquals(immutableListOf("A", "B", "C"), setOf("A", "D", "C")) + } + + @Test + fun size() { + assertEquals(2, ImmutableArrayList(arrayOf("A", "B")).size) + } + + @Test + fun isEmpty() { + assertFalse { + ImmutableArrayList(arrayOf("A", "B")).isEmpty() + } + } + + @Test + fun indexOf() { + assertEquals(1, ImmutableArrayList(arrayOf("A", "B", "C")).indexOf("B")) + assertEquals(-1, ImmutableArrayList(arrayOf("A", "B", "C")).indexOf("D")) + } + + @Test + fun iterator() { + val iterator = ImmutableArrayList(arrayOf("A", "B", "C")).iterator() + assertTrue { iterator.hasNext() } + assertEquals("A", iterator.next()) + assertTrue { iterator.hasNext() } + assertEquals("B", iterator.next()) + assertTrue { iterator.hasNext() } + assertEquals("C", iterator.next()) + assertFalse { iterator.hasNext() } + } + + @Test + fun listIterator() { + val listIterator = ImmutableArrayList(arrayOf("A", "B", "C")).listIterator() + assertTrue { listIterator.hasNext() } + assertFalse { listIterator.hasPrevious() } + assertEquals("A", listIterator.next()) + assertTrue { listIterator.hasNext() } + assertTrue { listIterator.hasPrevious() } + assertEquals("A", listIterator.previous()) + assertTrue { listIterator.hasNext() } + assertFalse { listIterator.hasPrevious() } + } + + @Test + fun listIteratorWithIndex() { + val list = immutableListOf("A", "B", "C", "D") + val iterator = list.listIterator(1) + assertTrue(iterator.hasNext()) + assertEquals("B", iterator.next()) + assertTrue(iterator.hasNext()) + assertEquals("C", iterator.next()) + assertTrue(iterator.hasNext()) + assertEquals("D", iterator.next()) + assertFalse (iterator.hasNext()) + assertThrows { iterator.next() } + } + + @Test + fun subList() { + assertEquals(emptyList(), immutableListOf(1, 2, 3).subList(0, 0)) + assertEquals(listOf(1), immutableListOf(1, 2, 3).subList(0, 1)) + assertEquals(listOf(2), immutableListOf(1, 2, 3).subList(1, 2)) + // more in the sublist tests + } + + @Test + fun testToString() { + assertEquals("[A,B,C]", ImmutableArrayList(arrayOf("A", "B", "C")).toString()) + } + + @Test + fun contains() { + assertTrue { ImmutableArrayList(arrayOf("A", "B", "C")).contains("C") } + assertFalse { ImmutableArrayList(arrayOf("A", "B", "C")).contains("D") } + } + + @Test + fun containsAll() { + assertTrue { ImmutableArrayList(arrayOf("A", "B", "C")).containsAll(listOf("A", "B", "C")) } + assertTrue { ImmutableArrayList(arrayOf("A", "B", "C")).containsAll(listOf("A", "B")) } + assertFalse { ImmutableArrayList(arrayOf("A", "B", "C")).containsAll(listOf("A", "B", "C", "D")) } + assertTrue { ImmutableArrayList(arrayOf("A", "B", "C")).containsAll(listOf()) } + } + + @Test + fun lastIndexOf() { + assertEquals(-1, ImmutableArrayList.of("A", "B", "A").lastIndexOf("C")) + assertEquals(2, ImmutableArrayList.of("A", "B", "A").lastIndexOf("A")) + } + + @Test + fun get() { + assertEquals("A", immutableListOf("A", "B", "C")[0]) + assertEquals("B", immutableListOf("A", "B", "C")[1]) + assertThrows { + immutableListOf("A", "B", "C")[3] + } + } + + @Test + fun testEquals() { + assertEquals(immutableListOf(1,2,3), immutableListOf(1,2,3)) + assertEquals(immutableListOf(1,2,3), listOf(1,2,3)) + assertEquals(immutableListOf(1,2,3), arrayListOf(1,2,3)) + + assertNotEquals(immutableListOf(1,2,3), setOf(1,2,3)) + assertNotEquals(immutableListOf(1,2,3), emptyList()) + assertNotEquals(immutableListOf(1,2,3), "") + } + + @Test + fun testHashCode() { + // when two objects are equal, their hashcode should also be equal + assertEquals(immutableListOf("A", "B").hashCode(), ArrayList(listOf("A", "B")).hashCode()) + assertEquals(immutableListOf("A", "B").hashCode(), arrayListOf("A", "B").hashCode()) + } +} diff --git a/kroki-collections/src/test/kotlin/io/github/kerubistan/kroki/collections/ImmutableSubArrayListTest.kt b/kroki-collections/src/test/kotlin/io/github/kerubistan/kroki/collections/ImmutableSubArrayListTest.kt new file mode 100644 index 0000000..cbe2bb4 --- /dev/null +++ b/kroki-collections/src/test/kotlin/io/github/kerubistan/kroki/collections/ImmutableSubArrayListTest.kt @@ -0,0 +1,155 @@ +package io.github.kerubistan.kroki.collections + +import io.kotest.assertions.throwables.shouldThrowAny +import io.kotest.assertions.withClue +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class ImmutableSubArrayListTest { + + @Test + fun validations() { + assertThrows { + immutableListOf(1, 2, 3).subList(-1, 2) + } + assertThrows { + immutableListOf(1, 2, 3).subList(1, 5) + } + assertThrows { + immutableListOf(1, 2, 3).subList(1, 0) + } + } + + @Test + fun getSize() { + assertEquals(1, immutableListOf(1,2,3).subList(0,1).size) + // this is not even done by a sublist, just emptyList + assertEquals(0, immutableListOf(1,2,3).subList(0,0).size) + } + + @Test + fun get() { + kotlin.test.assertEquals(0, immutableListOf(0, 1, 2).subList(0, 1)[0]) + kotlin.test.assertEquals(1, immutableListOf(0, 1, 2).subList(0, 2)[1]) + } + + @Test + fun isEmpty() { + assertFalse(immutableListOf("A", "B", "C").subList(1, 3).isEmpty()) + assertTrue(immutableListOf("A", "B", "C").subList(3, 3).isEmpty()) + } + + @Test + operator fun iterator() { + immutableListOf(0,1,2,3).subList(1,4).iterator().let { + assertTrue(it.hasNext()) + assertEquals(1, it.next()) + assertTrue(it.hasNext()) + assertEquals(2, it.next()) + assertTrue(it.hasNext()) + assertEquals(3, it.next()) + + withClue("only 3 items in the list, no more left") { + assertFalse(it.hasNext()) + } + + withClue("There is no next element, the call to next should now fail") { + assertThrows { it.next() } + } + } + } + + @Test + fun listIterator() { + immutableListOf(0,1,2,3).subList(1,4).listIterator().let { + assertFalse(it.hasPrevious()) + assertThrows { it.previous() } + assertTrue(it.hasNext()) + assertEquals(1, it.next()) + assertTrue(it.hasPrevious()) + assertTrue(it.hasNext()) + } + } + + @Test + fun testListIterator() { + immutableListOf(1,2,3).listIterator().let { + it.hasPrevious() shouldBe false + shouldThrowAny { + it.previous() + } + it.hasNext() shouldBe true + it.next() shouldBe 1 + + it.hasPrevious() shouldBe true + it.hasNext() shouldBe true + it.next() shouldBe 2 + + it.hasPrevious() shouldBe true + it.hasNext() shouldBe true + it.next() shouldBe 3 + + it.hasPrevious() shouldBe true + it.hasNext() shouldBe false + shouldThrowAny { + it.next() + } + } + } + + @Test + fun subList() { + assertEquals( + listOf("C", "D"), + immutableListOf("A", "B", "C", "D").subList(1, 4).subList(1, 3) + ) + } + + @Test + fun lastIndexOf() { + immutableListOf(1,2,3,4).lastIndexOf(3) shouldBe 2 + immutableListOf(1,2,3,3).lastIndexOf(3) shouldBe 3 + immutableListOf(1,2,3,3).lastIndexOf(4) shouldBe -1 + } + + @Test + fun indexOf() { + immutableListOf(1,2,3,4).indexOf(3) shouldBe 2 + immutableListOf(1,2,3,3).indexOf(3) shouldBe 2 + immutableListOf(1,2,3,3).indexOf(0) shouldBe -1 + } + + @Test + fun containsAll() { + assertTrue { immutableListOf(0,1,2,3,4).subList(1,3).containsAll(setOf(1,2)) } + } + + @Test + fun contains() { + assertTrue { immutableListOf(0,1,2,3,4).subList(0,2).contains(0) } + assertTrue { immutableListOf(0,1,2,3,4).subList(0,2).contains(1) } + assertTrue { immutableListOf(0,1,2,3,4).subList(1,3).contains(1) } + } + + @Test + fun testToString() { + assertEquals("[A,B,C]", immutableListOf("A", "B", "C").subList(0,3).toString()) + } + + @Test + fun testHashCode() { + assertEquals( + listOf(1,2,3,4).hashCode(), + immutableListOf(0,1,2,3,4).subList(1,5).hashCode() + ) + } + + @Test + fun testEquals() { + assertTrue(immutableListOf(0,1,2,3).subList(0,0) == emptyList()) + assertTrue(immutableListOf(0,1,2,3).subList(0,1) == listOf(0)) + } + +} \ No newline at end of file diff --git a/kroki-jmh/pom.xml b/kroki-jmh/pom.xml index 1ba6608..5fb31f9 100644 --- a/kroki-jmh/pom.xml +++ b/kroki-jmh/pom.xml @@ -30,6 +30,11 @@ kroki-utils 1.24-SNAPSHOT + + io.github.kerubistan.kroki + kroki-collections + 1.24-SNAPSHOT + io.github.kerubistan.kroki kroki-coroutines @@ -60,6 +65,11 @@ jackson-databind ${jackson.version} + + com.google.guava + guava + 32.1.3-jre + diff --git a/kroki-jmh/src/main/kotlin/io/github/kerubistan/kroki/benchmark/collections/ImmutableArrayListBenchmark.kt b/kroki-jmh/src/main/kotlin/io/github/kerubistan/kroki/benchmark/collections/ImmutableArrayListBenchmark.kt new file mode 100644 index 0000000..3704216 --- /dev/null +++ b/kroki-jmh/src/main/kotlin/io/github/kerubistan/kroki/benchmark/collections/ImmutableArrayListBenchmark.kt @@ -0,0 +1,95 @@ +package io.github.kerubistan.kroki.benchmark.collections + +import com.google.common.collect.ImmutableList +import io.github.kerubistan.kroki.collections.immutableListOf +import io.github.kerubistan.kroki.collections.immutableSorted +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Param +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State +import org.openjdk.jmh.infra.Blackhole +import java.util.* + +@State(Scope.Benchmark) +open class ImmutableArrayListBenchmark { + + @Param("1", "16", "1024", "4096") + var size: Int = 0 + + @Param("arraylist", "immutablearraylist", "guava") + var type: String = "arraylist" + + lateinit var list: List + + @Setup + fun setup() { + val rawList: MutableList = ArrayList(size) + for (i in 0..size) { + rawList.add(i.toString()) + } + rawList.shuffle() + when (type) { + "arraylist" -> { + list = ArrayList(rawList) + } + + "immutablearraylist" -> { + list = immutableListOf(*rawList.toTypedArray()) + } + "guava" -> { + list = ImmutableList.builder().addAll(rawList).build() + } + } + } + + @Benchmark + fun iterateWithForEach(blackhole: Blackhole) { + list.forEach(blackhole::consume) + } + + @Benchmark + fun iterateWithForLoop(blackhole: Blackhole) { + for (item in list) { + blackhole.consume(item) + } + } + + /** + * An example of what a faster list could do. + */ + @Benchmark + fun map(blackhole: Blackhole) { + blackhole.consume(list.map { it.lowercase() }) + } + + @Benchmark + fun callToString(blackhole: Blackhole) { + blackhole.consume(list.toString()) + } + + @Benchmark + fun first(blackhole: Blackhole) { + blackhole.consume(list.first()) + } + + @Benchmark + fun sorted(blackhole: Blackhole) { + blackhole.consume(list.sorted()) + } + + @Benchmark + fun immutableSorted(blackhole: Blackhole) { + blackhole.consume(list.immutableSorted()) + } + + @Benchmark + fun max(blackhole: Blackhole) { + blackhole.consume(list.max()) + } + + @Benchmark + fun callHashCode(blackhole: Blackhole) { + blackhole.consume(list.hashCode()) + } +} \ No newline at end of file diff --git a/kroki-jmh/src/main/kotlin/io/github/kerubistan/kroki/benchmark/collections/ImmutableSubArrayListBenchmark.kt b/kroki-jmh/src/main/kotlin/io/github/kerubistan/kroki/benchmark/collections/ImmutableSubArrayListBenchmark.kt new file mode 100644 index 0000000..aa24f5f --- /dev/null +++ b/kroki-jmh/src/main/kotlin/io/github/kerubistan/kroki/benchmark/collections/ImmutableSubArrayListBenchmark.kt @@ -0,0 +1,33 @@ +package io.github.kerubistan.kroki.benchmark.collections + +import io.github.kerubistan.kroki.collections.immutableListOf +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Param +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State +import org.openjdk.jmh.infra.Blackhole + +@State(Scope.Benchmark) +open class ImmutableSubArrayListBenchmark { + + @Param("immutable", "list") + var type = "list" + + lateinit var list : List + + @Setup + fun setup() { + list = when(type) { + "immutable" -> immutableListOf( *((1..100).map { it.toString() }.toList().toTypedArray()) ) + "list" -> listOf( *((1..100).map { it.toString() }.toList().toTypedArray()) ) + else -> throw IllegalArgumentException("immutable or list") + } + } + + @Benchmark + fun subList(blackhole: Blackhole) { + blackhole.consume(list.subList(1, 10)) + } + +} \ No newline at end of file diff --git a/kroki-jmh/src/main/kotlin/io/github/kerubistan/kroki/benchmark/collections/ListBuilderBenchmark.kt b/kroki-jmh/src/main/kotlin/io/github/kerubistan/kroki/benchmark/collections/ListBuilderBenchmark.kt new file mode 100644 index 0000000..7205bc2 --- /dev/null +++ b/kroki-jmh/src/main/kotlin/io/github/kerubistan/kroki/benchmark/collections/ListBuilderBenchmark.kt @@ -0,0 +1,38 @@ +package io.github.kerubistan.kroki.benchmark.collections + +import com.google.common.collect.ImmutableList +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Param +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.State +import org.openjdk.jmh.infra.Blackhole + +@State(Scope.Benchmark) +open class ListBuilderBenchmark { + + @Param("0", "1", "2", "16", "1024", "4096") + var size = 0 + + @Benchmark + fun buildImmutableArrayList(blackhole: Blackhole) { + blackhole.consume( + io.github.kerubistan.kroki.collections.buildList { + repeat(size) { + add(it) + } + } + ) + } + + @Benchmark + fun buildGuavaImmutableList(blackhole: Blackhole) { + blackhole.consume( + ImmutableList.builder().apply { + repeat(size) { + add(it) + } + }.build() + ) + } + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 48d54da..a850423 100644 --- a/pom.xml +++ b/pom.xml @@ -224,6 +224,7 @@ kroki-utils kroki-jmh kroki-coroutines + kroki-collections kroki-xml kroki-flyweight kroki-jdbc