From 05b3dc2b053bbff3569f5533b7898c8c936ca82b Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Thu, 19 Apr 2018 16:59:20 -0500 Subject: [PATCH] misc version upgrades --- build.gradle | 14 +- .../com/github/sanity/shoebox/KeyValue.kt | 3 - .../github/sanity/shoebox/OrderedViewSet.kt | 104 ---------- .../com/github/sanity/shoebox/Shoebox.kt | 173 ---------------- .../kotlin/com/github/sanity/shoebox/Store.kt | 11 -- .../kotlin/com/github/sanity/shoebox/View.kt | 152 -------------- .../github/sanity/shoebox/samples/samples.kt | 36 ---- .../sanity/shoebox/stores/DirectoryStore.kt | 137 ------------- .../sanity/shoebox/stores/MemoryStore.kt | 29 --- .../kotlin/com/github/sanity/shoebox/utils.kt | 83 -------- .../sanity/shoebox/OrderedViewSetSpec.kt | 187 ------------------ .../com/github/sanity/shoebox/ShoeboxSpec.kt | 136 ------------- .../com/github/sanity/shoebox/UtilsKtSpec.kt | 13 -- .../com/github/sanity/shoebox/ViewSpec.kt | 136 ------------- .../com/github/sanity/shoebox/data/Gender.kt | 5 - .../com/github/sanity/shoebox/data/User.kt | 3 - .../shoebox/stores/DirectoryStoreSpec.kt | 78 -------- 17 files changed, 4 insertions(+), 1296 deletions(-) delete mode 100644 src/main/kotlin/com/github/sanity/shoebox/KeyValue.kt delete mode 100644 src/main/kotlin/com/github/sanity/shoebox/OrderedViewSet.kt delete mode 100644 src/main/kotlin/com/github/sanity/shoebox/Shoebox.kt delete mode 100644 src/main/kotlin/com/github/sanity/shoebox/Store.kt delete mode 100644 src/main/kotlin/com/github/sanity/shoebox/View.kt delete mode 100644 src/main/kotlin/com/github/sanity/shoebox/samples/samples.kt delete mode 100644 src/main/kotlin/com/github/sanity/shoebox/stores/DirectoryStore.kt delete mode 100644 src/main/kotlin/com/github/sanity/shoebox/stores/MemoryStore.kt delete mode 100644 src/main/kotlin/com/github/sanity/shoebox/utils.kt delete mode 100644 src/test/kotlin/com/github/sanity/shoebox/OrderedViewSetSpec.kt delete mode 100644 src/test/kotlin/com/github/sanity/shoebox/ShoeboxSpec.kt delete mode 100644 src/test/kotlin/com/github/sanity/shoebox/UtilsKtSpec.kt delete mode 100644 src/test/kotlin/com/github/sanity/shoebox/ViewSpec.kt delete mode 100644 src/test/kotlin/com/github/sanity/shoebox/data/Gender.kt delete mode 100644 src/test/kotlin/com/github/sanity/shoebox/data/User.kt delete mode 100644 src/test/kotlin/com/github/sanity/shoebox/stores/DirectoryStoreSpec.kt diff --git a/build.gradle b/build.gradle index b474599..4f8c7de 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,9 @@ -group 'com.github.sanity' -version '0.2.13' +group 'io.kweb' +version '0.2.14' buildscript { - ext.kotlin_version = '1.1.3' - ext.dokka_version = '0.9.9' + ext.kotlin_version = '1.2.40' + ext.dokka_version = '0.9.15' repositories { jcenter() @@ -33,12 +33,6 @@ apply plugin: "info.solidsoft.pitest" repositories { mavenCentral() - maven { - url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" - } - maven { - url "http://dl.bintray.com/kotlin/kotlinx.coroutines" - } maven { url "https://dl.bintray.com/wasabifx/wasabifx" } diff --git a/src/main/kotlin/com/github/sanity/shoebox/KeyValue.kt b/src/main/kotlin/com/github/sanity/shoebox/KeyValue.kt deleted file mode 100644 index 11b705e..0000000 --- a/src/main/kotlin/com/github/sanity/shoebox/KeyValue.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.github.sanity.shoebox - -data class KeyValue(val key: String, val value: V) \ No newline at end of file diff --git a/src/main/kotlin/com/github/sanity/shoebox/OrderedViewSet.kt b/src/main/kotlin/com/github/sanity/shoebox/OrderedViewSet.kt deleted file mode 100644 index ef6c987..0000000 --- a/src/main/kotlin/com/github/sanity/shoebox/OrderedViewSet.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.github.sanity.shoebox - -import java.util.concurrent.ConcurrentHashMap - -/** - * Created by ian on 3/14/17. - */ -class OrderedViewSet(val view : View, val viewKey : String, val comparator: Comparator) { - - private val orderedList : MutableList> - private val modificationHandlers = ConcurrentHashMap() - private val additionHandle: Long - private val removalHandle: Long - - init { - val ol = ArrayList>() - val kvComparator : Comparator> = Comparator> { o1, o2 -> comparator.compare(o1.value, o2.value) }.thenBy(KeyValue::key) - ol.addAll(view.getKeyValues(viewKey)) - ol.sortWith(kvComparator) - orderedList = ol - additionHandle = view.onAdd(viewKey) { keyValue -> - val binarySearchResult = orderedList.betterBinarySearch(keyValue, kvComparator) - val insertionPoint: Int = when (binarySearchResult) { - is BinarySearchResult.Exact -> { - throw RuntimeException("Listener called for key/value already in list keyValue: $keyValue orderedList[${binarySearchResult.index}] = ${orderedList[binarySearchResult.index]}") - } - is BinarySearchResult.Between -> binarySearchResult.highIndex - } - ol.add(insertionPoint, keyValue) - insertListeners.values.forEach { it(insertionPoint, keyValue) } - } - - removalHandle = view.onRemove(viewKey) { keyValue -> - if (keyValue.value != null) { - val binarySearchResult = orderedList.betterBinarySearch(keyValue as KeyValue, kvComparator) - when (binarySearchResult) { - is BinarySearchResult.Exact -> { - removeListeners.values.forEach { it(binarySearchResult.index, keyValue) } - orderedList.removeAt(binarySearchResult.index) - } - is BinarySearchResult.Between -> throw RuntimeException("remove listener called for unknown value") - } - } else { - // On very rare occasions the View callback doesn't supply the value that was removed, in this case - // there isn't much we can do, so just ignore it - } - } - - ol.forEach { kv -> - modificationHandlers.put(kv.key, view.viewOf.onChange(kv.key) {oldValue, newValue, _ -> - if (comparator.compare(oldValue, newValue) != 0) { - val newKeyValue = KeyValue(kv.key, newValue) - val insertPoint = orderedList.betterBinarySearch(newKeyValue, kvComparator) - val insertionIndex: Int = when (insertPoint) { - is BinarySearchResult.Exact -> throw RuntimeException("Object modified to same value as an existing object ($newValue)") - is BinarySearchResult.Between -> insertPoint.highIndex - } - insertListeners.values.forEach { it(insertionIndex, newKeyValue) } - - val oldKeyValue = KeyValue(kv.key, oldValue) - val removePoint = orderedList.betterBinarySearch(oldKeyValue, kvComparator) - val removalIndex = when (removePoint) { - is BinarySearchResult.Exact -> removePoint.index - is BinarySearchResult.Between -> throw RuntimeException("Object modified from an unknown value ($oldValue)") - } - removeListeners.values.forEach { it(removalIndex, oldKeyValue) } - } - }) - } - } - - private val insertListeners = ConcurrentHashMap) -> Unit>() - private val removeListeners = ConcurrentHashMap) -> Unit>() - - val entries : List get() = keyValueEntries.map(KeyValue::value) - - val keyValueEntries : List> = orderedList - - fun onInsert(listener : (Int, KeyValue) -> Unit) : Long { - val handle = listenerHandleSource.incrementAndGet() - insertListeners.put(handle, listener) - return handle - } - - fun deleteInsertListener(handle : Long) { - insertListeners.remove(handle) - } - - fun onRemove(listener : (Int, KeyValue) -> Unit) : Long { - val handle = listenerHandleSource.incrementAndGet() - removeListeners.put(handle, listener) - return handle - } - - fun deleteRemoveListener(handle : Long) { - removeListeners.remove(handle) - } - - protected fun finalize() { - view.deleteAddListener(viewKey, additionHandle) - view.deleteRemoveListener(viewKey, removalHandle) - modificationHandlers.forEach { key, handler -> view.viewOf.deleteChangeListener(key, handler) } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/sanity/shoebox/Shoebox.kt b/src/main/kotlin/com/github/sanity/shoebox/Shoebox.kt deleted file mode 100644 index 4e41291..0000000 --- a/src/main/kotlin/com/github/sanity/shoebox/Shoebox.kt +++ /dev/null @@ -1,173 +0,0 @@ -package com.github.sanity.shoebox - -import com.github.sanity.shoebox.stores.DirectoryStore -import com.github.sanity.shoebox.stores.MemoryStore -import java.nio.file.Path -import java.util.concurrent.ConcurrentHashMap -import kotlin.reflect.KClass - - - /* - * TODO: 1) Add a lockfile mechanism to prevent multiple JVMs or threads from - * TODO: using the same directory - * TODO: 2) Handle changes that occur to the filesystem which aren't initiated here - * TODO: (then remove the previous lockfile mechanism) - */ - -/** - * Create a [Shoebox], use this in preference to the Shoebox constructor to avoid having to provide a `KClass` - * - * @param T The type of the objects to store, these must be serializable with [Gson](https://github.com/google/gson), - * - * @param directory The path to a directory in which data will be stored, will be created if it doesn't already exist - * - * @sample com.github.sanity.shoebox.samples.basic usage sample - **/ -inline fun Shoebox(store : Store) = Shoebox(store, T::class) -inline fun Shoebox(dir : Path) = Shoebox(DirectoryStore(dir), T::class) -inline fun Shoebox() = Shoebox(MemoryStore(), T::class) - - -/** - * Can persistently store and retrieve objects, and notify listeners of changes to those objects - * - * @constructor You probably want to use `Shoebox(directory)` instead - * @param T The type of the objects to store, these must be serializable with [Gson](https://github.com/google/gson), - * @param directory The path to a directory in which data will be stored, will be created if it doesn't already exist - * @param kc The KClass associated with T. To avoid having to provide this use `Shoebox(directory)` - */ -class Shoebox(val store: Store, private val kc: KClass) { - - private val keySpecificChangeListeners = ConcurrentHashMap Unit>>() - private val newListeners = ConcurrentHashMap, Source) -> Unit>() - private val removeListeners = ConcurrentHashMap, Source) -> Unit>() - private val changeListeners = ConcurrentHashMap, Source) -> Unit>() - - /** - * Retrieve a value, similar to [Map.get] - * - * @param key The key associated with the desired value - * @return The value associated with the key, or null if no value is associated - */ - operator fun get(key: String): T? { - return store.get(key) - } - - /** - * Remove a key-value pair - * - * @param key The key associated with the value to be removed, similar to [MutableMap.remove] - */ - fun remove(key: String) : T? { - val removed = store.remove(key) - if (removed != null) { - removeListeners.values.forEach { it.invoke(KeyValue(key, removed), Source.LOCAL) } - } - return removed - } - - /** - * Set or change a value, simliar to [MutableMap.set] - * - * @param key The key associated with the value to be set or changed - * @param value The new value - */ - operator fun set(key: String, value: T) { - val previousValue = store.set(key, value) - if (previousValue == null) { - newListeners.values.forEach { l -> l(KeyValue(key, value), Source.LOCAL) } - } else if (value != previousValue) { - changeListeners.values.forEach { cl -> cl(previousValue, KeyValue(key, value), Source.LOCAL) } - keySpecificChangeListeners[key]?.values?.forEach { l -> l(previousValue, value, Source.LOCAL) } - } - } - - /** - * A utility method to make it easier to modify an existing item - */ - fun modify(key : String, modifier : (T) -> T) : Boolean { - val oldValue = this[key] - return if (oldValue == null) { - false - } else { - this[key] = modifier(oldValue) - true - } - } - - val entries get() = store.entries - - /** - * Add a listener for when a new key-value pair are added to the Shoebox - * - * @param listener The listener to be called - */ - fun onNew(listener: (KeyValue, Source) -> Unit) : Long { - val handle = listenerHandleSource.incrementAndGet() - newListeners.put(handle, listener) - return handle - } - - fun deleteNewListener(handle : Long) { - newListeners.remove(handle) - } - - fun onRemove(listener: (KeyValue, Source) -> Unit) : Long { - val handle = listenerHandleSource.incrementAndGet() - removeListeners.put(handle, listener) - return handle - } - - fun deleteRemoveListener(handle : Long) { - removeListeners.remove(handle) - } - - fun onChange(listener: (T, KeyValue, Source) -> Unit) : Long { - val handle = listenerHandleSource.incrementAndGet() - changeListeners.put(handle, listener) - return handle - } - - fun onChange(key: String, listener: (T, T, Source) -> Unit) : Long { - val handle = listenerHandleSource.incrementAndGet() - keySpecificChangeListeners.computeIfAbsent(key, { ConcurrentHashMap() }).put(handle, listener) - return handle - } - - fun deleteChangeListener(handle : Long) { - changeListeners.remove(handle) - } - - fun deleteChangeListener(key: String, handle : Long) { - keySpecificChangeListeners[key]?.let { - it.remove(handle) - if (it.isEmpty()) { - keySpecificChangeListeners.remove(key) - } - } - } - - fun view(name : String, by : (T) -> String, verify : View.VerifyBehavior = View.VerifyBehavior.BLOCKING_VERIFY) : View { - val store = when (store) { - is MemoryStore -> MemoryStore() - is DirectoryStore -> - DirectoryStore(store.directory.parent.resolve("${store.directory.fileName.toString()}-$name-view")) - else -> throw RuntimeException("Shoebox doesn't currently support creating a view for store type ${store::class.simpleName}") - } - return View(Shoebox(store), this, verify, by) - } -} - -/** - * The source of the event that generated this change - */ -enum class Source { - /** - * The event was due to a modification initiated by a call to this instance's [Shoebox.set] - */ - LOCAL, - /** - * The event was due to a filesystem change external to this instance - */ - REMOTE -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/sanity/shoebox/Store.kt b/src/main/kotlin/com/github/sanity/shoebox/Store.kt deleted file mode 100644 index 4a31a8f..0000000 --- a/src/main/kotlin/com/github/sanity/shoebox/Store.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.sanity.shoebox - -/** - * Created by ian on 3/22/17. - */ -interface Store { - val entries: Iterable> - fun remove(key: String): T? - operator fun get(key: String): T? - operator fun set(key: String, value: T) : T? -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/sanity/shoebox/View.kt b/src/main/kotlin/com/github/sanity/shoebox/View.kt deleted file mode 100644 index d961f0b..0000000 --- a/src/main/kotlin/com/github/sanity/shoebox/View.kt +++ /dev/null @@ -1,152 +0,0 @@ -package com.github.sanity.shoebox - -import java.util.* -import java.util.concurrent.ConcurrentHashMap -import kotlin.concurrent.thread - -/** - * Created by ian on 3/11/17. - */ - -class View(val references: Shoebox, - val viewOf: Shoebox, - val verifyBehavior: VerifyBehavior = VerifyBehavior.BLOCKING_VERIFY, - val viewBy: (T) -> String) { - - private val newListenerHandler: Long - private val changeListenerHandler: Long - private var removeListenerHandler: Long - - init { - newListenerHandler = viewOf.onNew { keyValue, source -> - val viewKey = viewBy(keyValue.value) - if (source == Source.LOCAL) { - addValue(viewKey, keyValue.key) - } - addListeners[viewKey]?.values?.forEach { it(keyValue) } - - } - changeListenerHandler = viewOf.onChange { previousValue, nextKeyValue, source -> - if (source == Source.LOCAL) { - if (previousValue != nextKeyValue.value) { - val previousViewKey = viewBy(previousValue) - val nextViewKey = viewBy(nextKeyValue.value) - if (previousViewKey != nextViewKey) { - - removeListeners[previousViewKey]?.values?.forEach { it(KeyValue(nextKeyValue.key, previousValue)) } - removeValue(previousViewKey, nextKeyValue.key) - - addListeners[nextViewKey]?.values?.forEach { it(nextKeyValue) } - addValue(nextViewKey, nextKeyValue.key) - } - } - } - } - removeListenerHandler = viewOf.onRemove { keyValue, source -> - if (source == Source.LOCAL) { - val viewKey = viewBy(keyValue.value) - removeListeners[viewKey]?.values?.forEach { it(keyValue as KeyValue) } - removeValue(viewKey, keyValue.key) - } - } - - when (verifyBehavior) { - VerifyBehavior.BLOCKING_VERIFY -> verify() - VerifyBehavior.ASYNC_VERIFY -> thread { verify() } - } - } - - private fun verify() { - for ((key, value) in viewOf.entries) { - val refKey = viewBy(value) - addValue(refKey, key) - } - - // NOTE: We don't check for superfluous references because these are found and corrected in get() - } - - operator fun get(viewKey: String): Set = getKeyValues(viewKey).map(KeyValue::value).toSet() - - fun getKeyValues(viewKey: String): Set> { - val reference = references[viewKey] - return reference?.keys?.mapNotNull { key -> - val v = viewOf[key] - if (v == null) { - removeListeners[viewKey]?.values?.forEach { it(KeyValue(key, null)) } - removeValue(viewKey, key) - null - } else if (viewBy(v) != viewKey) { - removeListeners[viewKey]?.values?.forEach { it(KeyValue(key, null)) } - removeValue(viewKey, key) - null - } else { - KeyValue(key, v) - } - }?.toSet() ?: Collections.emptySet() - } - - private val addListeners = ConcurrentHashMap) -> Unit>>() - - fun onAdd(viewKey : String, listener : (KeyValue) -> Unit) : Long { - val handle = listenerHandleSource.incrementAndGet() - addListeners.computeIfAbsent(viewKey, {ConcurrentHashMap()}).put(handle, listener) - return handle - } - - fun deleteAddListener(viewKey : String, handle : Long) { - addListeners.get(viewKey)?.remove(handle) - } - - private val removeListeners = ConcurrentHashMap) -> Unit>>() - - fun onRemove(viewKey : String, listener : (KeyValue) -> Unit) : Long { - val handle = listenerHandleSource.incrementAndGet() - removeListeners.computeIfAbsent(viewKey, {ConcurrentHashMap()}).put(handle, listener) - return handle - } - - fun deleteRemoveListener(viewKey : String, handle : Long) { - removeListeners.get(viewKey)?.remove(handle) - } - - protected fun finalize() { - viewOf.deleteNewListener(newListenerHandler) - viewOf.deleteChangeListener(changeListenerHandler) - viewOf.deleteRemoveListener(removeListenerHandler) - } - - sealed class EventType { - data class Add(val key : String, val obj : T) : EventType() - data class Remove(val key : String, val obj : T?) : EventType() - } - - internal fun addValue(key: String, value: String) { - val oldRef = references[key] ?: Reference() - references[key] = oldRef.addKey(value) - } - - internal fun removeValue(key: String, value: String) { - val oldRef = references[key] - if (oldRef != null) { - references[key] = oldRef.removeKey(value) - } - } - - enum class VerifyBehavior { - BLOCKING_VERIFY, ASYNC_VERIFY - } - - data class Reference(val keys: Set) { - constructor() : this(Collections.emptySet()) - - fun removeKey(key: String) = Reference(keys.minus(key)) - - fun addKey(key: String) = Reference(keys.plus(key)) - } - - fun orderedSet(key : String, comparator : Comparator = compareBy {it.hashCode()}) = OrderedViewSet(this, key, comparator) -} - - - - diff --git a/src/main/kotlin/com/github/sanity/shoebox/samples/samples.kt b/src/main/kotlin/com/github/sanity/shoebox/samples/samples.kt deleted file mode 100644 index e29970d..0000000 --- a/src/main/kotlin/com/github/sanity/shoebox/samples/samples.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.sanity.shoebox.samples - -import com.github.sanity.shoebox.Shoebox -import com.github.sanity.shoebox.View -import java.nio.file.Files - -/** - * Created by ian on 3/9/17. - */ - - -fun `basic usage sample`() { - val dir = Files.createTempDirectory("sb-") - val userStore = Shoebox(dir.resolve("users")) - val usersByEmail = View(Shoebox(dir.resolve("usersByEmail")), userStore, viewBy = User::email) - val usersByGender = View(Shoebox(dir.resolve("usersByGender")), userStore, viewBy = User::gender) - - userStore["ian"] = User("Ian Clarke", "male", "ian@blah.com") - userStore["fred"] = User("Fred Smith", "male", "fred@blah.com") - userStore["sue"] = User("Sue Smith", "female", "sue@blah.com") - - println(usersByEmail["ian@blah.com"]) // [User(name=Ian Clarke, gender=male, email=ian@blah.com)] - println(usersByGender["male"]) // [User(name=Ian Clarke, gender=male, email=ian@blah.com), - // User(name=Fred Smith, gender=male, email=fred@blah.com)] - // note: view["xx]" returns a set of values - usersByGender.onAdd("male", {kv -> - println("${kv.key} became male") - }) - usersByGender.onRemove("male", {kv -> - println("${kv.key} ceased to be male") - }) - - userStore["fred"] = userStore["fred"]!!.copy(gender = "female") // Prints "fred ceased to be male" -} - -data class User(val name : String, val gender : String, val email : String) \ No newline at end of file diff --git a/src/main/kotlin/com/github/sanity/shoebox/stores/DirectoryStore.kt b/src/main/kotlin/com/github/sanity/shoebox/stores/DirectoryStore.kt deleted file mode 100644 index b9b7877..0000000 --- a/src/main/kotlin/com/github/sanity/shoebox/stores/DirectoryStore.kt +++ /dev/null @@ -1,137 +0,0 @@ -package com.github.sanity.shoebox.stores - -import com.github.sanity.shoebox.* -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader -import com.google.common.cache.LoadingCache -import com.google.gson.GsonBuilder -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.attribute.FileTime -import java.util.concurrent.TimeUnit -import kotlin.reflect.KClass - -/** - * Created by ian on 3/22/17. - */ - -inline fun DirectoryStore(directory : Path) = DirectoryStore(directory, T::class) - -class DirectoryStore(val directory : Path, private val kc : KClass) : Store { - companion object { - const private val LOCK_FILENAME = "shoebox.lock" - const private val LOCK_TOUCH_TIME_MS = 2000.toLong() - const private val LOCK_STALE_TIME = LOCK_TOUCH_TIME_MS * 2 - private val gson = GsonBuilder().create() - } - - internal val cache: LoadingCache = CacheBuilder.newBuilder().build( - object : CacheLoader() { - override fun load(key: String): T? { - return this@DirectoryStore.load(key) - } - } - ) - - private val lockFilePath = directory.resolve(LOCK_FILENAME) - - init { - Files.createDirectories(directory) - if (Files.exists(lockFilePath)) { - if (System.currentTimeMillis() - Files.getLastModifiedTime(lockFilePath).toMillis() < LOCK_STALE_TIME) { - throw RuntimeException("$directory locked by $lockFilePath") - } else { - Files.setLastModifiedTime(lockFilePath, FileTime.fromMillis(System.currentTimeMillis())) - } - } else { - Files.newBufferedWriter(lockFilePath).use { - it.appendln("locked") - } - } - scheduledExecutor.scheduleWithFixedDelay({ - Files.setLastModifiedTime(lockFilePath, FileTime.fromMillis(System.currentTimeMillis())) - }, LOCK_TOUCH_TIME_MS, LOCK_TOUCH_TIME_MS, TimeUnit.MILLISECONDS) - } - - /** - * Retrieve the entries in this store, similar to [Map.entries] but lazy - * - * @return The keys and their corresponding values in this [Shoebox] - */ - override val entries: Iterable> get() = Files.newDirectoryStream(directory) - .mapNotNull {it.fileName.toString()} - .filter {it != LOCK_FILENAME} - .map { - KeyValue(it, this[it]!!) - } - - /** - * Retrieve a value, similar to [Map.get] - * - * @param key The key associated with the desired value - * @return The value associated with the key, or null if no value is associated - */ - override operator fun get(key: String): T? { - return load(key) - } - - /** - * Remove a key-value pair - * - * @param key The key associated with the value to be removed, similar to [MutableMap.remove] - */ - override fun remove(key: String) : T? { - val cachedValue: T? = cache.getIfPresent(key) - if (cachedValue != null) { - cache.invalidate(key) - } - val filePath = directory.resolve(key) - if (Files.exists(filePath)) { - val oldValue = cachedValue ?: load(key) - if (oldValue != null) { - Files.delete(filePath) - return oldValue - } else { - return null - } - } else { - return null - } - } - - /** - * Set or change a value, simliar to [MutableMap.set] - * - * @param key The key associated with the value to be set or changed - * @param value The new value - */ - override operator fun set(key: String, value: T) : T? { - val previousValue = get(key) - cache.put(key, value) - if (value != previousValue) { - if (!directory.exists()) throw RuntimeException("Parent directory doesn't exist") - val filePath = directory.resolve(key) - filePath.newBufferedWriter().use { - gson.toJson(value, kc.javaObjectType, it) - } - } - return previousValue - } - - private fun load(key: String): T? { - val filePath = directory.resolve(key) - if (Files.exists(filePath)) { - val o = filePath.newBufferedReader().use { - gson.fromJson(it, kc.javaObjectType) - } - cache.put(key, o) - return o - } else { - return null - } - } - - protected fun finalize() { - Files.delete(lockFilePath) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/sanity/shoebox/stores/MemoryStore.kt b/src/main/kotlin/com/github/sanity/shoebox/stores/MemoryStore.kt deleted file mode 100644 index 4d92bb7..0000000 --- a/src/main/kotlin/com/github/sanity/shoebox/stores/MemoryStore.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.github.sanity.shoebox.stores - -import com.github.sanity.shoebox.KeyValue -import com.github.sanity.shoebox.Store -import java.util.concurrent.ConcurrentHashMap - -/** - * Created by ian on 3/22/17. - */ -class MemoryStore : Store { - private val map = ConcurrentHashMap() - - override val entries: Iterable> - get() = map.entries.map {KeyValue(it.key, it.value)} - - override fun remove(key: String): T? { - return map.remove(key) - } - - override fun get(key: String): T? { - return map.get(key) - } - - override fun set(key: String, value: T): T? { - val previousVal = map.get(key) - map.set(key, value) - return previousVal - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/sanity/shoebox/utils.kt b/src/main/kotlin/com/github/sanity/shoebox/utils.kt deleted file mode 100644 index 2ad7ef9..0000000 --- a/src/main/kotlin/com/github/sanity/shoebox/utils.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.github.sanity.shoebox - -import java.nio.file.Files -import java.nio.file.OpenOption -import java.nio.file.Path -import java.util.* -import java.util.concurrent.Executors -import java.util.concurrent.atomic.AtomicLong - -/** - * Created by ian on 3/9/17. - */ - -val scheduledExecutor = Executors.newScheduledThreadPool(1) - -fun Path.newBufferedReader() = Files.newBufferedReader(this) - -fun Path.newBufferedWriter(vararg openOptions : OpenOption) = Files.newBufferedWriter(this, *openOptions) - -fun Path.exists() = Files.exists(this) - -fun Path.mkdirIfAbsent() : Path { - if (!this.exists()) { - Files.createDirectory(this) - } - return this -} - -val random = Random() - -val listenerHandleSource = AtomicLong(0) - - -fun List.betterBinarySearch(v: T, comparator: Comparator) = toBinarySearchResult(this.binarySearch(v, comparator)) - -fun List.toArrayList(): ArrayList { - return if (this is ArrayList) { - this - } else { - val r = ArrayList() - r.addAll(this) - r - } -} - -sealed class BinarySearchResult { - class Exact(val index: Int) : BinarySearchResult() { - override fun equals(other: Any?): Boolean { - return if (other is Exact) { - index == other.index - } else { - false - } - } - - override fun toString(): String { - return "Exact($index)" - } - } - class Between(val lowIndex: Int, val highIndex: Int) : BinarySearchResult() { - override fun equals(other: Any?): Boolean { - return if (other is Between) { - lowIndex == other.lowIndex && highIndex == other.highIndex - } else { - false - } - } - - override fun toString(): String { - return "Between($lowIndex, $highIndex)" - } - } -} - - -private fun toBinarySearchResult(result: Int): BinarySearchResult { - return if (result >= 0) { - BinarySearchResult.Exact(result) - } else { - val insertionPoint = -result - 1 - BinarySearchResult.Between(insertionPoint - 1, insertionPoint) - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/sanity/shoebox/OrderedViewSetSpec.kt b/src/test/kotlin/com/github/sanity/shoebox/OrderedViewSetSpec.kt deleted file mode 100644 index c753e83..0000000 --- a/src/test/kotlin/com/github/sanity/shoebox/OrderedViewSetSpec.kt +++ /dev/null @@ -1,187 +0,0 @@ -package com.github.sanity.shoebox - -import com.github.sanity.shoebox.data.Gender -import com.github.sanity.shoebox.data.User -import com.github.sanity.shoebox.stores.MemoryStore -import io.kotlintest.matchers.shouldBe -import io.kotlintest.matchers.shouldEqual -import io.kotlintest.specs.FreeSpec - -/** - * Created by ian on 3/14/17. - */ -class OrderedViewSetSpec : FreeSpec() { - init { - "an OrderedViewSet" - { - - "on initialization" - { - val userMap = Shoebox(MemoryStore()) - userMap["zool"] = User("Zool", Gender.MALE) - userMap["george"] = User("George", Gender.MALE) - userMap["paul"] = User("Paul", Gender.MALE) - userMap["xavier"] = User("Xavier", Gender.MALE) - userMap["jack"] = User("Jack", Gender.MALE) - userMap["jill"] = User("Jill", Gender.FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = { it.gender.toString() }) - - val maleViewSet = OrderedViewSet(viewByGender, "MALE", compareBy(User::name)) - val menInOrder = listOf( - KeyValue("george", User("George", Gender.MALE)), - KeyValue("jack", User("Jack", Gender.MALE)), - KeyValue("paul", User("Paul", Gender.MALE)), - KeyValue("xavier", User("Xavier", Gender.MALE)), - KeyValue("zool", User("Zool", Gender.MALE)) - ) - "keyValueEntries should return men in correct order" { - maleViewSet.keyValueEntries shouldEqual menInOrder - } - "entries should return men in correct order" { - maleViewSet.entries shouldEqual menInOrder.map { it.value } - } - - val femaleViewSet = OrderedViewSet(viewByGender, "FEMALE", compareBy(User::name)) - femaleViewSet.keyValueEntries shouldEqual listOf(KeyValue("jill", User("Jill", Gender.FEMALE))) - } - - "when a value is added" - { - val userMap = Shoebox(MemoryStore()) - userMap["jack"] = User("Jack", Gender.MALE) - userMap["paul"] = User("Paul", Gender.MALE) - userMap["jill"] = User("Jill", Gender.FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = { it.gender.toString() }) - - val maleViewSet = OrderedViewSet(viewByGender, "MALE", compareBy(User::name)) - - var callCount = 0 - val insertHandle = maleViewSet.onInsert { ix, keyValue -> - callCount++ - "should call the insert handler with the correct values" { - callCount shouldBe 1 - ix shouldBe 2 - keyValue shouldBe KeyValue("peter", User("Peter", Gender.MALE)) - } - } - userMap["peter"] = User("Peter", Gender.MALE) - "should call the insert handler" { - callCount shouldBe 1 - } - - "should include newly inserted value in keyValueEntries" { - maleViewSet.keyValueEntries shouldEqual listOf( - KeyValue("jack", User("Jack", Gender.MALE)), - KeyValue("paul", User("Paul", Gender.MALE)), - KeyValue("peter", User("Peter", Gender.MALE)) - ) - } - - "should not call the insert handler after it has been deleted" { - maleViewSet.deleteInsertListener(insertHandle) - userMap["toby"] = User("Toby", Gender.MALE) - callCount shouldBe 1 - } - } - - "when a value is deleted" - { - val userMap = Shoebox(MemoryStore()) - userMap["jack"] = User("Jack", Gender.MALE) - userMap["paul"] = User("Paul", Gender.MALE) - userMap["jill"] = User("Jill", Gender.FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = { it.gender.toString() }) - - val maleViewSet = OrderedViewSet(viewByGender, "MALE", compareBy(User::name)) - - var callCount = 0 - val removeHandle = maleViewSet.onRemove { ix, keyValue -> - callCount++ - "should call the delete handler with the correct values" { - callCount shouldBe 1 - ix shouldBe 0 - keyValue shouldBe KeyValue("jack", User("Jack", Gender.MALE)) - } - } - userMap.remove("jack") - "should call the remove handler" { - callCount shouldBe 1 - } - - "shouldn't include newly removed value in keyValueEntries" { - maleViewSet.keyValueEntries shouldEqual listOf( - KeyValue("paul", User("Paul", Gender.MALE)) - ) - } - - "should not call the handler after it has been deleted" { - maleViewSet.deleteRemoveListener(removeHandle) - userMap.remove("paul") - callCount shouldBe 1 - } - } - - " when a second value is added that is not distinguishable based on the supplied comparator" { - val userMap = Shoebox(MemoryStore()) - userMap["jack"] = User("Jack", Gender.MALE) - userMap["jill"] = User("Jill", Gender.FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = { it.gender.toString() }) - - val maleViewSet = OrderedViewSet(viewByGender, "MALE", compareBy(User::name)) - - maleViewSet.onInsert { p, kv -> - - } - - userMap["paul"] = User("Paul", Gender.MALE) - } - - "should detect a value reorder" - { - val userMap = Shoebox(MemoryStore()) - val jackUser = User("Jack", Gender.MALE) - userMap["jack"] = jackUser - userMap["paul"] = User("Paul", Gender.MALE) - userMap["jill"] = User("Jill", Gender.FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = { it.gender.toString() }) - - val maleViewSet = OrderedViewSet(viewByGender, "MALE", compareBy(User::name)) - - var callCount = 0 - val renamedJackUser = jackUser.copy(name = "Zeus") - maleViewSet.onInsert { ix, keyValue -> - callCount++ - "should call the insert handler with the correct values" { - ix shouldBe 2 - keyValue shouldBe KeyValue("jack", renamedJackUser) - } - } - maleViewSet.onRemove { ix, keyValue -> - callCount++ - "should call the remove handler with the correct values" { - ix shouldBe 0 - keyValue shouldBe KeyValue("jack", jackUser) - } - } - userMap["jack"] = renamedJackUser - - "should call both handlers" { - callCount shouldBe 2 - } - } - - "should handle this case discovered while debugging" - { - data class Dog(val name: String, val color: String, val breed: String) - - val dogs = Shoebox() - listOf( - Dog(name = "hot dog", color = "tan", breed = "dachshund"), - Dog(name = "toby", color = "tan", breed = "labrador") - ).forEach { dogs[it.name] = it } - - val viewByColor = dogs.view("dogsByColor", Dog::color) - val tanDogs = viewByColor.orderedSet("tan", compareBy(Dog::color)) - "dogs should be listed with correct test in correct order" { - tanDogs.entries.size shouldBe 2 - tanDogs.entries[0] shouldBe Dog(name = "hot dog", color = "tan", breed = "dachshund") - tanDogs.entries[1] shouldBe Dog(name = "toby", color = "tan", breed = "labrador") - } - } - } - } -} diff --git a/src/test/kotlin/com/github/sanity/shoebox/ShoeboxSpec.kt b/src/test/kotlin/com/github/sanity/shoebox/ShoeboxSpec.kt deleted file mode 100644 index f1cfdc6..0000000 --- a/src/test/kotlin/com/github/sanity/shoebox/ShoeboxSpec.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.github.sanity.shoebox - -import com.github.sanity.shoebox.stores.MemoryStore -import io.kotlintest.matchers.shouldBe -import io.kotlintest.matchers.shouldEqual -import io.kotlintest.specs.FreeSpec -import java.util.concurrent.atomic.AtomicInteger - -/** - * Created by ian on 3/12/17. - */ -class ShoeboxSpec : FreeSpec() { - - - init { - "A Shoebox store" - { - val object1 = TestData(1, 2) - val object2 = TestData(3, 4) - "when an item is stored" - { - val pm = Shoebox(MemoryStore()) - pm["key1"] = object1 - "should retrieve the data" { - val retrievedObject: TestData? = pm["key1"] - retrievedObject shouldEqual object1 - } - } - "when an item is removed" - { - val pm = Shoebox(MemoryStore()) - pm["key1"] = object1 - pm.remove("key1") - "should return null for the removed key" { - pm["key1"] shouldEqual null - } - } - "should iterate through data" { - val pm = Shoebox(MemoryStore()) - pm["key1"] = TestData(1, 2) - pm["key2"] = TestData(3, 4) - val entries = pm.entries - entries.map { KeyValue(it.key, it.value) }.toSet() shouldEqual setOf(KeyValue("key1", TestData(1, 2)), KeyValue("key2", TestData(3, 4))) - - } - - "should trigger appropriate callbacks when" - { - val object1 = TestData(1, 2) - val object2 = TestData(3, 4) - val object3 = TestData(5, 4) - "a new object is created" - { - val pm = Shoebox(MemoryStore()) - var callCount = AtomicInteger(0) - val handle: Long = pm.onNew { keyValue, source -> - callCount.incrementAndGet() shouldEqual 1 - keyValue shouldEqual KeyValue("key1", object1) - source shouldEqual Source.LOCAL - } - pm["key1"] = object1 - "should trigger callback" { callCount.get() shouldEqual 1 } - pm.deleteNewListener(handle) - "should not trigger callback after it has been removed" { - callCount.get() shouldEqual 1 - pm["key3"] = object3 - callCount.get() shouldEqual 1 - } - pm.remove("key3") - - } - "an object is changed" - { - val pm = Shoebox(MemoryStore()) - pm["key1"] = object1 - var globalCallCount = 0 - var keySpecificCallCount = 0 - val globalChangeHandle = pm.onChange { prev, nextKeyValue, source -> - globalCallCount++ - "global change callback should be called with the correct parameters" { - prev shouldEqual object1 - nextKeyValue shouldEqual KeyValue("key1", object2) - source shouldEqual Source.LOCAL - } - } - val keySpecificChangeHandle = pm.onChange("key1") { old, new, source -> - keySpecificCallCount++ - "key-specific change callback should be called with the correct parameters" { - old shouldEqual object1 - new shouldEqual object2 - source shouldBe Source.LOCAL - } - } - pm["key1"] = object2 - "callbacks should each be called once" { - globalCallCount shouldEqual 1 - keySpecificCallCount shouldEqual 1 - } - pm["key1"] = object2.copy() // Shouldn't trigger the callbacks again - "callbacks shouldn't be called again if the object value hasn't changed" { - globalCallCount shouldEqual 1 - keySpecificCallCount shouldEqual 1 - } - - pm.deleteChangeListener(globalChangeHandle) - pm.deleteChangeListener("key1", keySpecificChangeHandle) - pm["key1"] = object3 - "callbacks shouldn't be called after they've been removed" { - globalCallCount shouldEqual 1 - keySpecificCallCount shouldEqual 1 - } - - - } - "should trigger object removal callback" - { - val pm = Shoebox(MemoryStore()) - pm["key1"] = object3 - var callCount = 0 - val onRemoveHandle = pm.onRemove { keyValue, source -> - callCount++ - keyValue shouldEqual KeyValue("key1", object3) - source shouldEqual Source.LOCAL - - } - pm.remove("key1") - "callback should be called once" { - callCount shouldEqual 1 - } - pm.deleteRemoveListener(onRemoveHandle) - pm["key3"] = object3 - pm.remove("key3") - "callback shouldn't be called again after it has been removed" { - callCount shouldEqual 1 - } - } - } - } - } - - data class TestData(val one: Int, val two: Int) - -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/sanity/shoebox/UtilsKtSpec.kt b/src/test/kotlin/com/github/sanity/shoebox/UtilsKtSpec.kt deleted file mode 100644 index db4ce2a..0000000 --- a/src/test/kotlin/com/github/sanity/shoebox/UtilsKtSpec.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.sanity.shoebox - -import io.kotlintest.specs.FreeSpec -import java.nio.file.Files - -/** - * Created by ian on 3/12/17. - */ -class UtilsKtSpec : FreeSpec() { - init { - - } -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/sanity/shoebox/ViewSpec.kt b/src/test/kotlin/com/github/sanity/shoebox/ViewSpec.kt deleted file mode 100644 index daa3f5e..0000000 --- a/src/test/kotlin/com/github/sanity/shoebox/ViewSpec.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.github.sanity.shoebox - -import com.github.sanity.shoebox.data.Gender.FEMALE -import com.github.sanity.shoebox.data.Gender.MALE -import com.github.sanity.shoebox.data.User -import com.github.sanity.shoebox.stores.MemoryStore -import io.kotlintest.matchers.beEmpty -import io.kotlintest.matchers.should -import io.kotlintest.matchers.shouldEqual -import io.kotlintest.specs.FreeSpec -import java.lang.AssertionError -import java.util.concurrent.atomic.AtomicInteger - -/** - * Created by ian on 3/12/17. - */ -class ViewSpec : FreeSpec() { - init { - "on initialization" - { - val userMap = Shoebox(MemoryStore()) - userMap["jack"] = User("Jack", MALE) - userMap["jill"] = User("Jill", FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = {it.gender.toString()}) - "references should be correct" { - viewByGender.references["MALE"]!!.keys shouldEqual setOf("jack") - viewByGender.references["FEMALE"]!!.keys shouldEqual setOf("jill") - } - "should return correctly categorized objects" { - viewByGender["MALE"] shouldEqual setOf(User("Jack", MALE)) - viewByGender["FEMALE"] shouldEqual setOf(User("Jill", FEMALE)) - } - } - "on change of a view name after initialization" - { - val userMap = Shoebox(MemoryStore()) - userMap["jack"] = User("Jack", MALE) - userMap["jill"] = User("Jill", FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = {it.gender.toString()}) - - val addListener = CountingListener(KeyValue("jack", User("Jack", FEMALE))) - viewByGender.onAdd("MALE", addListener::add) // Should have no effect - viewByGender.onAdd("FEMALE", addListener::add) - - val removeListener = CountingListener(KeyValue("jack", User("Jack", MALE))) - viewByGender.onRemove("MALE", removeListener::remove) - viewByGender.onRemove("FEMALE", removeListener::remove) // Should have no effect - - userMap["jack"] = User("Jack", FEMALE) - - "references should be correct" { - viewByGender.references["MALE"]!!.keys should beEmpty() - viewByGender.references["FEMALE"]!!.keys shouldEqual setOf("jack", "jill") - - } - "actual values returned should be correct" { - viewByGender["FEMALE"] shouldEqual setOf(User("Jack", FEMALE), User("Jill", FEMALE)) - viewByGender["MALE"] should beEmpty() - } - "listeners should have been called" { - addListener.counter shouldEqual 1 - removeListener.counter shouldEqual 1 - } - } - - "should respond to a failure to sync a viewName change correctly" { - val userMap = Shoebox(MemoryStore()) - userMap["jack"] = User("Jack", MALE) - userMap["jill"] = User("Jill", FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = {it.gender.toString()}) - userMap["jack"] = User("Jack", FEMALE) - viewByGender.addValue("MALE", "jack") - viewByGender.references["MALE"]!!.keys shouldEqual setOf("jack") - viewByGender["MALE"] should beEmpty() - } - - "should respond to an addition correctly" { - val userMap = Shoebox(MemoryStore()) - userMap["jack"] = User("Jack", MALE) - userMap["jill"] = User("Jill", FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = {it.gender.toString()}) - val addListener = CountingListener(KeyValue("paul", User("Paul", MALE))) - viewByGender.onAdd("MALE", addListener::add) - userMap["paul"] = User("Paul", MALE) - viewByGender.references["MALE"]!!.keys shouldEqual setOf("jack", "paul") - viewByGender["MALE"] shouldEqual setOf(User("Paul", MALE), User("Jack", MALE)) - - viewByGender.references["FEMALE"]!!.keys shouldEqual setOf("jill") - viewByGender["FEMALE"] shouldEqual setOf(User("Jill", FEMALE)) - - addListener.counter shouldEqual 1 - } - - "should respond to a deletion correctly" { - val userMap = Shoebox(MemoryStore()) - userMap["jack"] = User("Jack", MALE) - userMap["jill"] = User("Jill", FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = {it.gender.toString()}) - val removeListener = CountingListener(KeyValue("jill", User("Jill", FEMALE))) - viewByGender.onRemove("FEMALE", removeListener::remove) - userMap.remove("jill") - viewByGender.references["FEMALE"]!!.keys should beEmpty() - viewByGender["FEMALE"] should beEmpty() - viewByGender.references["MALE"]!!.keys shouldEqual setOf("jack") - viewByGender["MALE"] shouldEqual setOf(User("Jack", MALE)) - - removeListener.counter shouldEqual 1 - } - "should correct for a failure to sync a delete" { - val userMap = Shoebox(MemoryStore()) - userMap["jack"] = User("Jack", MALE) - userMap["jill"] = User("Jill", FEMALE) - val viewByGender = View(Shoebox(MemoryStore()), viewOf = userMap, viewBy = {it.gender.toString()}) - userMap.remove("jill") - viewByGender.addValue("FEMALE", "jill") - viewByGender.references["FEMALE"]!!.keys shouldEqual setOf("jill") - viewByGender["FEMALE"] should beEmpty() - } - } - - class CountingListener(val correct : KeyValue) { - private val _counter = AtomicInteger(0) - - fun add(kv : KeyValue) { - _counter.incrementAndGet() - if (kv != correct) throw AssertionError("$kv != $correct") - } - - fun remove(kv : KeyValue) { - _counter.incrementAndGet() - if (kv != correct) throw AssertionError("$kv != $correct") - } - - val counter get() = _counter.get() - } -} - - diff --git a/src/test/kotlin/com/github/sanity/shoebox/data/Gender.kt b/src/test/kotlin/com/github/sanity/shoebox/data/Gender.kt deleted file mode 100644 index 8301f38..0000000 --- a/src/test/kotlin/com/github/sanity/shoebox/data/Gender.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.github.sanity.shoebox.data - -enum class Gender { - MALE, FEMALE -} \ No newline at end of file diff --git a/src/test/kotlin/com/github/sanity/shoebox/data/User.kt b/src/test/kotlin/com/github/sanity/shoebox/data/User.kt deleted file mode 100644 index 8104577..0000000 --- a/src/test/kotlin/com/github/sanity/shoebox/data/User.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.github.sanity.shoebox.data - -data class User(val name : String, val gender : Gender) \ No newline at end of file diff --git a/src/test/kotlin/com/github/sanity/shoebox/stores/DirectoryStoreSpec.kt b/src/test/kotlin/com/github/sanity/shoebox/stores/DirectoryStoreSpec.kt deleted file mode 100644 index e3ad394..0000000 --- a/src/test/kotlin/com/github/sanity/shoebox/stores/DirectoryStoreSpec.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.github.sanity.shoebox.stores - -import com.github.sanity.shoebox.ShoeboxSpec -import io.kotlintest.matchers.gt -import io.kotlintest.matchers.shouldBe -import io.kotlintest.matchers.shouldEqual -import io.kotlintest.matchers.shouldThrow -import io.kotlintest.specs.FreeSpec -import java.nio.file.Files -import java.nio.file.attribute.FileTime - -/** - * Created by ian on 3/22/17. - */ -class DirectoryStoreSpec : FreeSpec() { - init { - "DirectoryStore" - { - "locking" - { - val dir = Files.createTempDirectory("ss-") - val directoryStore = DirectoryStore(dir) - "should create a lockfile" { - Files.exists(dir.resolve("shoebox.lock")) shouldBe true - } - "should throw an exception if attempting to create a store for a directory that already has a store" { - shouldThrow { - DirectoryStore(dir) - } - } - "should disregard an old lock" { - val dir = Files.createTempDirectory("ss-") - val lockFilePath = dir.resolve("shoebox.lock") - Files.newBufferedWriter(lockFilePath).use { - it.appendln("lock") - } - Files.setLastModifiedTime(lockFilePath, FileTime.fromMillis(System.currentTimeMillis() - 60000)) - DirectoryStore(dir, ShoeboxSpec.TestData::class) - } - - "should update an old lock" { - val dir = Files.createTempDirectory("ss-") - val lockFilePath = dir.resolve("shoebox.lock") - Files.newBufferedWriter(lockFilePath).use { - it.appendln("lock") - } - Files.setLastModifiedTime(lockFilePath, FileTime.fromMillis(System.currentTimeMillis() - 60000)) - DirectoryStore(dir, ShoeboxSpec.TestData::class) - Files.getLastModifiedTime(lockFilePath).toMillis() shouldBe gt (System.currentTimeMillis() - 5000) - } - } - val object1 = ShoeboxSpec.TestData(1, 2) - val object2 = ShoeboxSpec.TestData(3, 4) - "when an item is stored" - { - val object1 = ShoeboxSpec.TestData(1, 2) - val dir = Files.createTempDirectory("ss-") - val pm = DirectoryStore(dir) - pm["key1"] = object1 - "should cache the item that was stored" { - pm.cache.get("key1") shouldEqual object1 - } - } - "when an item is replaced" - { - val dir = Files.createTempDirectory("ss-") - val pm = DirectoryStore(dir) - pm["key1"] = object1 - pm["key1"] = object2 - - "should have cached the replaced data" { - pm.cache.get("key1") shouldEqual object2 - } - "should retrieve the replaced data without the cache" { - pm.cache.invalidate("key1") - pm["key1"] shouldEqual object2 - } - } - } - } -} -