aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Jaros <rjaros@finn.pl>2019-04-18 22:48:36 +0200
committerRobert Jaros <rjaros@finn.pl>2019-04-18 22:48:36 +0200
commit6a92d7e67326eeec6040c94983b79f288a32a386 (patch)
treec04f6ef08ce3d6d28244c54f2d0cf14224b8cf81
parent276b1033fe81e2e66732201e43cadc69acc42f1b (diff)
downloadkvision-6a92d7e67326eeec6040c94983b79f288a32a386.tar.gz
kvision-6a92d7e67326eeec6040c94983b79f288a32a386.tar.bz2
kvision-6a92d7e67326eeec6040c94983b79f288a32a386.zip
Add internal cache for styles generation code.
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Css.kt10
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Style.kt34
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt362
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Widget.kt34
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/utils/Cache.kt198
5 files changed, 465 insertions, 173 deletions
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Css.kt b/src/main/kotlin/pl/treksoft/kvision/core/Css.kt
index 9fc50097..8a468fe5 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Css.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Css.kt
@@ -552,6 +552,8 @@ class Border private constructor(
val w = width?.asString()
return w.orEmpty() + " " + (style?.borderStyle).orEmpty() + " " + color.orEmpty()
}
+
+ override fun toString() = asString()
}
/**
@@ -573,6 +575,8 @@ class Color private constructor(private val color: String? = null) {
fun asString(): String {
return color.orEmpty()
}
+
+ override fun toString() = asString()
}
/**
@@ -676,6 +680,8 @@ class Background private constructor(
} + " " + (repeat?.repeat).orEmpty() + " " + (origin?.origin).orEmpty() + " " +
(clip?.clip).orEmpty() + " " + (attachment?.attachment).orEmpty()
}
+
+ override fun toString() = asString()
}
/**
@@ -717,6 +723,8 @@ class TextDecoration private constructor(
(style?.textDecorationStyle).orEmpty() + " " +
color.orEmpty()
}
+
+ override fun toString() = asString()
}
/**
@@ -764,4 +772,6 @@ class TextShadow private constructor(
(blurRadius?.asString()).orEmpty() + " " +
color.orEmpty()
}
+
+ override fun toString() = asString()
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Style.kt b/src/main/kotlin/pl/treksoft/kvision/core/Style.kt
index 549aa44c..282d2e7e 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Style.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Style.kt
@@ -26,6 +26,7 @@ import com.github.snabbdom.h
import org.w3c.dom.Node
import pl.treksoft.jquery.JQuery
import pl.treksoft.kvision.panel.Root
+import kotlin.reflect.KProperty
/**
* CSS style object.
@@ -38,6 +39,7 @@ import pl.treksoft.kvision.panel.Root
@Suppress("TooManyFunctions")
open class Style(className: String? = null, parentStyle: Style? = null, init: (Style.() -> Unit)? = null) :
StyledComponent() {
+ private val propertyValues: MutableMap<String, Any?> = mutableMapOf()
override var parent: Container? = Root.getFirstRoot()
@@ -133,6 +135,38 @@ open class Style(className: String? = null, parentStyle: Style? = null, init: (S
styles.remove(this)
}
+ protected fun <T> refreshOnUpdate(refreshFunction: ((T) -> Unit) = { this.refresh() }) =
+ RefreshDelegateProvider<T>(null, refreshFunction)
+
+ protected fun <T> refreshOnUpdate(initialValue: T, refreshFunction: ((T) -> Unit) = { this.refresh() }) =
+ RefreshDelegateProvider(initialValue, refreshFunction)
+
+ protected inner class RefreshDelegateProvider<T>(
+ private val initialValue: T?, private val refreshFunction: (T) -> Unit
+ ) {
+ operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): RefreshDelegate<T> {
+ if (initialValue != null) propertyValues[prop.name] = initialValue
+ return RefreshDelegate(refreshFunction)
+ }
+ }
+
+ protected inner class RefreshDelegate<T>(private val refreshFunction: ((T) -> Unit)) {
+ @Suppress("UNCHECKED_CAST")
+ operator fun getValue(thisRef: StyledComponent, property: KProperty<*>): T {
+ val value = propertyValues[property.name]
+ return if (value != null) {
+ value as T
+ } else {
+ null as T
+ }
+ }
+
+ operator fun setValue(thisRef: StyledComponent, property: KProperty<*>, value: T) {
+ propertyValues[property.name] = value
+ refreshFunction(value)
+ }
+ }
+
companion object {
internal var counter = 0
internal var styles = mutableListOf<Style>()
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
index 428728b7..1207ba5e 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
@@ -21,6 +21,7 @@
*/
package pl.treksoft.kvision.core
+import pl.treksoft.kvision.utils.Cache
import pl.treksoft.kvision.utils.asString
import kotlin.reflect.KProperty
@@ -311,185 +312,196 @@ abstract class StyledComponent : Component {
*/
@Suppress("ComplexMethod", "LongMethod")
protected open fun getSnStyle(): List<StringPair> {
- val snstyle = mutableListOf<StringPair>()
- width?.let {
- snstyle.add("width" to it.asString())
- }
- minWidth?.let {
- snstyle.add("min-width" to it.asString())
- }
- maxWidth?.let {
- snstyle.add("max-width" to it.asString())
- }
- height?.let {
- snstyle.add("height" to it.asString())
- }
- minHeight?.let {
- snstyle.add("min-height" to it.asString())
- }
- maxHeight?.let {
- snstyle.add("max-height" to it.asString())
- }
- display?.let {
- snstyle.add("display" to it.display)
- }
- position?.let {
- snstyle.add("position" to it.position)
- }
- top?.let {
- snstyle.add("top" to it.asString())
- }
- left?.let {
- snstyle.add("left" to it.asString())
- }
- right?.let {
- snstyle.add("right" to it.asString())
- }
- bottom?.let {
- snstyle.add("bottom" to it.asString())
- }
- zIndex?.let {
- snstyle.add("z-index" to it.toString())
- }
- overflow?.let {
- snstyle.add("overflow" to it.overflow)
- }
- overflowWrap?.let {
- snstyle.add("overflow-wrap" to it.overflowWrap)
- }
- resize?.let {
- snstyle.add("resize" to it.resize)
- }
- border?.let {
- snstyle.add("border" to it.asString())
- }
- borderTop?.let {
- snstyle.add("border-top" to it.asString())
- }
- borderRight?.let {
- snstyle.add("border-right" to it.asString())
- }
- borderBottom?.let {
- snstyle.add("border-bottom" to it.asString())
- }
- borderLeft?.let {
- snstyle.add("border-left" to it.asString())
- }
- margin?.let {
- snstyle.add("margin" to it.asString())
- }
- marginTop?.let {
- snstyle.add("margin-top" to it.asString())
- }
- marginRight?.let {
- snstyle.add("margin-right" to it.asString())
- }
- marginBottom?.let {
- snstyle.add("margin-bottom" to it.asString())
- }
- marginLeft?.let {
- snstyle.add("margin-left" to it.asString())
- }
- padding?.let {
- snstyle.add("padding" to it.asString())
- }
- paddingTop?.let {
- snstyle.add("padding-top" to it.asString())
- }
- paddingRight?.let {
- snstyle.add("padding-right" to it.asString())
- }
- paddingBottom?.let {
- snstyle.add("padding-bottom" to it.asString())
- }
- paddingLeft?.let {
- snstyle.add("padding-left" to it.asString())
- }
- color?.let {
- snstyle.add("color" to it.asString())
- }
- opacity?.let {
- snstyle.add("opacity" to it.toString())
- }
- background?.let {
- snstyle.add("background" to it.asString())
- }
- textDirection?.let {
- snstyle.add("direction" to it.direction)
- }
- letterSpacing?.let {
- snstyle.add("letter-spacing" to it.asString())
- }
- lineHeight?.let {
- snstyle.add("line-height" to it.asString())
- }
- textAlign?.let {
- snstyle.add("text-align" to it.textAlign)
- }
- textDecoration?.let {
- snstyle.add("text-decoration" to it.asString())
- }
- textIndent?.let {
- snstyle.add("text-indent" to it.asString())
- }
- textShadow?.let {
- snstyle.add("text-shadow" to it.asString())
- }
- textTransform?.let {
- snstyle.add("text-transform" to it.textTransform)
- }
- textOverflow?.let {
- snstyle.add("text-overflow" to it.textOverflow)
- }
- unicodeBidi?.let {
- snstyle.add("unicode-bidi" to it.unicodeBidi)
- }
- verticalAlign?.let {
- snstyle.add("vartical-align" to it.verticalAlign)
- }
- whiteSpace?.let {
- snstyle.add("white-space" to it.whiteSpace)
- }
- wordSpacing?.let {
- snstyle.add("word-spacing" to it.asString())
- }
- fontFamily?.let {
- snstyle.add("font-family" to it)
- }
- fontSize?.let {
- snstyle.add("font-size" to it.asString())
- }
- fontStyle?.let {
- snstyle.add("font-style" to it.fontStyle)
- }
- fontWeight?.let {
- snstyle.add("font-weight" to it.fontWeight)
- }
- fontVariant?.let {
- snstyle.add("font-variant" to it.fontVariant)
- }
- float?.let {
- snstyle.add("float" to it.posFloat)
- }
- clear?.let {
- snstyle.add("clear" to it.clear)
- }
- wordBreak?.let {
- snstyle.add("word-break" to it.wordBreak)
- }
- lineBreak?.let {
- snstyle.add("line-break" to it.lineBreak)
+ val cacheKey = getCacheKey()
+ return globalStyleCache[cacheKey] ?: run {
+ val snstyle = mutableListOf<StringPair>()
+ width?.let {
+ snstyle.add("width" to it.asString())
+ }
+ minWidth?.let {
+ snstyle.add("min-width" to it.asString())
+ }
+ maxWidth?.let {
+ snstyle.add("max-width" to it.asString())
+ }
+ height?.let {
+ snstyle.add("height" to it.asString())
+ }
+ minHeight?.let {
+ snstyle.add("min-height" to it.asString())
+ }
+ maxHeight?.let {
+ snstyle.add("max-height" to it.asString())
+ }
+ display?.let {
+ snstyle.add("display" to it.display)
+ }
+ position?.let {
+ snstyle.add("position" to it.position)
+ }
+ top?.let {
+ snstyle.add("top" to it.asString())
+ }
+ left?.let {
+ snstyle.add("left" to it.asString())
+ }
+ right?.let {
+ snstyle.add("right" to it.asString())
+ }
+ bottom?.let {
+ snstyle.add("bottom" to it.asString())
+ }
+ zIndex?.let {
+ snstyle.add("z-index" to it.toString())
+ }
+ overflow?.let {
+ snstyle.add("overflow" to it.overflow)
+ }
+ overflowWrap?.let {
+ snstyle.add("overflow-wrap" to it.overflowWrap)
+ }
+ resize?.let {
+ snstyle.add("resize" to it.resize)
+ }
+ border?.let {
+ snstyle.add("border" to it.asString())
+ }
+ borderTop?.let {
+ snstyle.add("border-top" to it.asString())
+ }
+ borderRight?.let {
+ snstyle.add("border-right" to it.asString())
+ }
+ borderBottom?.let {
+ snstyle.add("border-bottom" to it.asString())
+ }
+ borderLeft?.let {
+ snstyle.add("border-left" to it.asString())
+ }
+ margin?.let {
+ snstyle.add("margin" to it.asString())
+ }
+ marginTop?.let {
+ snstyle.add("margin-top" to it.asString())
+ }
+ marginRight?.let {
+ snstyle.add("margin-right" to it.asString())
+ }
+ marginBottom?.let {
+ snstyle.add("margin-bottom" to it.asString())
+ }
+ marginLeft?.let {
+ snstyle.add("margin-left" to it.asString())
+ }
+ padding?.let {
+ snstyle.add("padding" to it.asString())
+ }
+ paddingTop?.let {
+ snstyle.add("padding-top" to it.asString())
+ }
+ paddingRight?.let {
+ snstyle.add("padding-right" to it.asString())
+ }
+ paddingBottom?.let {
+ snstyle.add("padding-bottom" to it.asString())
+ }
+ paddingLeft?.let {
+ snstyle.add("padding-left" to it.asString())
+ }
+ color?.let {
+ snstyle.add("color" to it.asString())
+ }
+ opacity?.let {
+ snstyle.add("opacity" to it.toString())
+ }
+ background?.let {
+ snstyle.add("background" to it.asString())
+ }
+ textDirection?.let {
+ snstyle.add("direction" to it.direction)
+ }
+ letterSpacing?.let {
+ snstyle.add("letter-spacing" to it.asString())
+ }
+ lineHeight?.let {
+ snstyle.add("line-height" to it.asString())
+ }
+ textAlign?.let {
+ snstyle.add("text-align" to it.textAlign)
+ }
+ textDecoration?.let {
+ snstyle.add("text-decoration" to it.asString())
+ }
+ textIndent?.let {
+ snstyle.add("text-indent" to it.asString())
+ }
+ textShadow?.let {
+ snstyle.add("text-shadow" to it.asString())
+ }
+ textTransform?.let {
+ snstyle.add("text-transform" to it.textTransform)
+ }
+ textOverflow?.let {
+ snstyle.add("text-overflow" to it.textOverflow)
+ }
+ unicodeBidi?.let {
+ snstyle.add("unicode-bidi" to it.unicodeBidi)
+ }
+ verticalAlign?.let {
+ snstyle.add("vartical-align" to it.verticalAlign)
+ }
+ whiteSpace?.let {
+ snstyle.add("white-space" to it.whiteSpace)
+ }
+ wordSpacing?.let {
+ snstyle.add("word-spacing" to it.asString())
+ }
+ fontFamily?.let {
+ snstyle.add("font-family" to it)
+ }
+ fontSize?.let {
+ snstyle.add("font-size" to it.asString())
+ }
+ fontStyle?.let {
+ snstyle.add("font-style" to it.fontStyle)
+ }
+ fontWeight?.let {
+ snstyle.add("font-weight" to it.fontWeight)
+ }
+ fontVariant?.let {
+ snstyle.add("font-variant" to it.fontVariant)
+ }
+ float?.let {
+ snstyle.add("float" to it.posFloat)
+ }
+ clear?.let {
+ snstyle.add("clear" to it.clear)
+ }
+ wordBreak?.let {
+ snstyle.add("word-break" to it.wordBreak)
+ }
+ lineBreak?.let {
+ snstyle.add("line-break" to it.lineBreak)
+ }
+ globalStyleCache[cacheKey] = snstyle
+ return snstyle
}
- return snstyle
}
- protected fun <T> refreshOnUpdate(refreshFunction: ((T) -> Unit) = { this.refresh() }) =
+ protected open fun getCacheKey(): String {
+ val SEP = "###KvSep###"
+ return propertyValues.map {
+ it.toString()
+ }.joinToString(SEP)
+ }
+
+ private fun <T> refreshOnUpdate(refreshFunction: ((T) -> Unit) = { this.refresh() }) =
RefreshDelegateProvider<T>(null, refreshFunction)
- protected fun <T> refreshOnUpdate(initialValue: T, refreshFunction: ((T) -> Unit) = { this.refresh() }) =
+ private fun <T> refreshOnUpdate(initialValue: T, refreshFunction: ((T) -> Unit) = { this.refresh() }) =
RefreshDelegateProvider(initialValue, refreshFunction)
- protected inner class RefreshDelegateProvider<T>(
+ private inner class RefreshDelegateProvider<T>(
private val initialValue: T?, private val refreshFunction: (T) -> Unit
) {
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): RefreshDelegate<T> {
@@ -498,7 +510,7 @@ abstract class StyledComponent : Component {
}
}
- protected inner class RefreshDelegate<T>(private val refreshFunction: ((T) -> Unit)) {
+ private inner class RefreshDelegate<T>(private val refreshFunction: ((T) -> Unit)) {
@Suppress("UNCHECKED_CAST")
operator fun getValue(thisRef: StyledComponent, property: KProperty<*>): T {
val value = propertyValues[property.name]
@@ -514,4 +526,8 @@ abstract class StyledComponent : Component {
refreshFunction(value)
}
}
+
+ companion object {
+ internal val globalStyleCache = Cache<String, List<StringPair>>()
+ }
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
index 9bf84209..f0be38cb 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
@@ -42,6 +42,7 @@ import pl.treksoft.kvision.utils.snAttrs
import pl.treksoft.kvision.utils.snClasses
import pl.treksoft.kvision.utils.snOpt
import pl.treksoft.kvision.utils.snStyle
+import kotlin.reflect.KProperty
/**
* Base widget class. The parent of all component classes.
@@ -53,6 +54,7 @@ import pl.treksoft.kvision.utils.snStyle
*/
@Suppress("TooManyFunctions", "LargeClass")
open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
+ private val propertyValues: MutableMap<String, Any?> = mutableMapOf()
internal val classes = classes.toMutableSet()
internal val surroundingClasses: MutableSet<String> = mutableSetOf()
@@ -765,6 +767,38 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
override fun dispose() {
}
+ protected fun <T> refreshOnUpdate(refreshFunction: ((T) -> Unit) = { this.refresh() }) =
+ RefreshDelegateProvider<T>(null, refreshFunction)
+
+ protected fun <T> refreshOnUpdate(initialValue: T, refreshFunction: ((T) -> Unit) = { this.refresh() }) =
+ RefreshDelegateProvider(initialValue, refreshFunction)
+
+ protected inner class RefreshDelegateProvider<T>(
+ private val initialValue: T?, private val refreshFunction: (T) -> Unit
+ ) {
+ operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): RefreshDelegate<T> {
+ if (initialValue != null) propertyValues[prop.name] = initialValue
+ return RefreshDelegate(refreshFunction)
+ }
+ }
+
+ protected inner class RefreshDelegate<T>(private val refreshFunction: ((T) -> Unit)) {
+ @Suppress("UNCHECKED_CAST")
+ operator fun getValue(thisRef: StyledComponent, property: KProperty<*>): T {
+ val value = propertyValues[property.name]
+ return if (value != null) {
+ value as T
+ } else {
+ null as T
+ }
+ }
+
+ operator fun setValue(thisRef: StyledComponent, property: KProperty<*>, value: T) {
+ propertyValues[property.name] = value
+ refreshFunction(value)
+ }
+ }
+
companion object {
/**
* DSL builder extension function.
diff --git a/src/main/kotlin/pl/treksoft/kvision/utils/Cache.kt b/src/main/kotlin/pl/treksoft/kvision/utils/Cache.kt
new file mode 100644
index 00000000..6a473661
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/utils/Cache.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision.utils
+
+/*
+ * Copyright 2018 Nazmul Idris All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This is a LRU cache that has no performance impact for cache insertions
+ * once the capacity of the cache has been reached. For cache hit,
+ * performance is O(1) and for cache eviction, it is O(1).
+ */
+class Cache<K, V>(private val capacity: Int = 50) {
+ private val cache = HashMap<K, V>()
+ private val insertionOrder = LinkedList<K>()
+
+ /**
+ * [HashMap] put and remove is O(1).
+ * More info: https://stackoverflow.com/a/4578039/2085356
+ */
+ operator fun set(key: K, value: V): K? {
+ var evictedKey: K? = null
+ if (cache.size >= capacity) {
+ evictedKey = getKeyToEvict()
+ cache.remove(evictedKey)
+ }
+ cache[key] = value
+ insertionOrder.append(key)
+ return evictedKey
+ }
+
+ /**
+ * [HashMap] get is O(1).
+ * More info: https://stackoverflow.com/a/4578039/2085356
+ */
+ operator fun get(key: K): V? = cache[key]
+
+ /**
+ * The head of the [insertionOrder] is removed, which is O(1), since this
+ * is a linked list, and it's inexpensive to remove an item from head.
+ * More info: https://stackoverflow.com/a/42849573/2085356
+ */
+ private fun getKeyToEvict(): K? = insertionOrder.removeAtIndex(0)
+
+ override fun toString() = cache.toString()
+}
+
+/**
+ * Created by gazollajunior on 07/04/16.
+ */
+class Node<T>(var value: T) {
+ var next: Node<T>? = null
+ var previous: Node<T>? = null
+}
+
+class LinkedList<T> {
+
+ private var head: Node<T>? = null
+
+ var isEmpty: Boolean = head == null
+
+ fun first(): Node<T>? = head
+
+ fun last(): Node<T>? {
+ var node = head
+ if (node != null) {
+ while (node?.next != null) {
+ node = node.next
+ }
+ return node
+ } else {
+ return null
+ }
+ }
+
+ fun count(): Int {
+ var node = head
+ if (node != null) {
+ var counter = 1
+ while (node?.next != null) {
+ node = node.next
+ counter += 1
+ }
+ return counter
+ } else {
+ return 0
+ }
+ }
+
+ fun nodeAtIndex(index: Int): Node<T>? {
+ if (index >= 0) {
+ var node = head
+ var i = index
+ while (node != null) {
+ if (i == 0) return node
+ i -= 1
+ node = node.next
+ }
+ }
+ return null
+ }
+
+ fun append(value: T) {
+ val newNode = Node(value)
+
+ val lastNode = this.last()
+ if (lastNode != null) {
+ newNode.previous = lastNode
+ lastNode.next = newNode
+ } else {
+ head = newNode
+ }
+ }
+
+ fun removeAll() {
+ head = null
+ }
+
+ fun removeNode(node: Node<T>): T {
+ val prev = node.previous
+ val next = node.next
+
+ if (prev != null) {
+ prev.next = next
+ } else {
+ head = next
+ }
+ next?.previous = prev
+
+ node.previous = null
+ node.next = null
+
+ return node.value
+ }
+
+ fun removeLast(): T? {
+ val last = this.last()
+ if (last != null) {
+ return removeNode(last)
+ } else {
+ return null
+ }
+ }
+
+ fun removeAtIndex(index: Int): T? {
+ val node = nodeAtIndex(index)
+ if (node != null) {
+ return removeNode(node)
+ } else {
+ return null
+ }
+ }
+
+ override fun toString(): String {
+ var s = "["
+ var node = head
+ while (node != null) {
+ s += "${node.value}"
+ node = node.next
+ if (node != null) {
+ s += ", "
+ }
+ }
+ return s + "]"
+ }
+}