Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collections #9

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
72 changes: 72 additions & 0 deletions kroki-collections/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>kroki</artifactId>
<groupId>io.github.kerubistan.kroki</groupId>
<version>1.24-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>

<artifactId>kroki-collections</artifactId>
<packaging>jar</packaging>

<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
</license>
</licenses>

<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>io.github.kerubistan.kroki</groupId>
<artifactId>kroki-delegates</artifactId>
<version>1.24-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.kotest</groupId>
<artifactId>kotest-assertions-core-jvm</artifactId>
<version>5.9.1</version>
</dependency>
</dependencies>

<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.github.kerubistan.kroki.collections

fun <T : Any> immutableListOf(vararg items: T): List<T> =
when (items.size) {
0 -> emptyList()
1 -> listOf(items.single())
else -> ImmutableArrayList(items)
}

inline fun <reified T : Any> List<T>.toImmutableList(): List<T> = when {
this.isEmpty() -> emptyList()
this.size == 1 -> listOf(first())
else -> immutableListOf(*(this.toTypedArray()))
}

internal fun <T> listHashCode(list: List<T>): Int {
var hashCode = 1
list.forEach { hashCode = (hashCode * 31) + it.hashCode() }
return hashCode
}

fun <T : Comparable<T>> List<T>.immutableSorted(): List<T> =
when (this) {
is ImmutableArrayList -> this.sortedToImmutable()
else -> this.sorted()
}

inline fun <reified T : Any> buildList(builder: ImmutableListBuilder<T>.() -> Unit) =
ImmutableListBuilder<T>().apply(builder).build()
Original file line number Diff line number Diff line change
@@ -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<T : Any>() : List<T> {

internal lateinit var items: Array<out T>

internal constructor(array: Array<out T>) : this() {
this.items = array
}

companion object {
fun <T : Any> of(vararg items: T): List<T> = 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<out T : Any>(private val items: Array<T>) : ListIterator<T> {
private var index = 0

constructor(items: Array<T>, 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<T> = ImmutableArrayListIterator(this.items)

override fun listIterator(): ListIterator<T> = ImmutableArrayListIterator(this.items)

override fun listIterator(index: Int): ListIterator<T> = ImmutableArrayListIterator(this.items, index)

override fun subList(fromIndex: Int, toIndex: Int): List<T> =
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<T>): 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('[')
[email protected] { index, it ->
if (index != 0) {
append(',')
}
append(it)
}
append(']')
}
}

override fun toString(): String = stringValue

internal fun sortedToImmutable(): List<T> =
ImmutableArrayList(
this.items.clone().apply { sort() }
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.github.kerubistan.kroki.collections

class ImmutableListBuilder<T : Any> {
private var increment : Int = 128
private var size : Int = 0
private var items : Array<Any?> = 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<T>) {
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<T> =
when (size) {
0 -> emptyList()
items.size -> {
ImmutableArrayList(items as Array<T>)
}
else -> {
ImmutableArrayList((items.copyOf(size)) as Array<T>)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<T : Any>(
private val offset: Int,
private val limit: Int,
private val items: Array<T>
) : List<T> {

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<T>(
private val items: Array<T>,
private val offset: Int,
private val limit: Int,
private var position: Int = offset
) : ListIterator<T> {
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<T> = SubListIterator(items, offset, limit)

override fun listIterator(): ListIterator<T> = SubListIterator(items, offset, limit)

override fun listIterator(index: Int): ListIterator<T> = SubListIterator(items, offset, limit, index)

override fun subList(fromIndex: Int, toIndex: Int): List<T> =
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<T>): 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('[')
[email protected] { 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

}
Loading
Loading