diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/AbstractStorage.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/AbstractStorage.kt index 263222091..21df64bac 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/AbstractStorage.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/AbstractStorage.kt @@ -1,7 +1,12 @@ package com.itangcent.idea.plugin.utils import com.itangcent.idea.plugin.utils.Storage.Companion.DEFAULT_GROUP +import com.itangcent.idea.plugin.utils.Storage.Companion.NULL +import java.util.* +/** + * Abstract implementation of [Storage] + */ abstract class AbstractStorage : Storage { override fun get(name: String?): Any? { @@ -36,4 +41,93 @@ abstract class AbstractStorage : Storage { clear(DEFAULT_GROUP) } + override fun get(group: String?, name: String?): Any? { + return getCache(group ?: DEFAULT_GROUP)[name] + } + + override fun set(group: String?, name: String?, value: Any?) { + useCache(group) { put(name ?: NULL, value) } + } + + override fun pop(group: String?, name: String?): Any? { + return useQueue(group, name) { pollLast() } + } + + override fun peek(group: String?, name: String?): Any? { + return useQueue(group, name) { peekLast() } + } + + override fun push(group: String?, name: String?, value: Any?) { + useQueue(group, name) { addLast(value) } + } + + override fun remove(group: String?, name: String) { + useCache(group) { remove(name) } + } + + override fun keys(group: String?): Array { + return getCache(group ?: DEFAULT_GROUP).keys.toTypedArray() + } + + override fun clear(group: String?) { + useCache(group ?: DEFAULT_GROUP) { clear() } + } + + /** + * Use cache and update cache + */ + @Synchronized + private fun useCache(group: String?, action: MutableMap.() -> T?): T? { + val cache = getCache(group ?: DEFAULT_GROUP) + try { + return cache.action() + } finally { + onUpdate(group, cache) + } + } + + /** + * Use queue and update cache + */ + @Synchronized + @Suppress("UNCHECKED_CAST") + private fun useQueue(group: String?, name: String?, action: LinkedList.() -> T?): T? { + val cache = getCache(group ?: DEFAULT_GROUP) + var queue = cache[name ?: NULL] + try { + when (queue) { + + is LinkedList<*> -> { + return action(queue as LinkedList) + } + + is Collection<*> -> { + queue = LinkedList(queue) + cache[name ?: NULL] = queue + return action(queue) + } + + else -> { + queue = LinkedList() + cache[name ?: NULL] = queue + return action(queue) + } + } + } finally { + if (queue is Collection<*> && queue.isEmpty()) { + cache.remove(name ?: NULL) + } + onUpdate(group, cache) + } + } + + /** + * Get cache of specified group + */ + protected abstract fun getCache(group: String): MutableMap + + /** + * called when cache is updated + */ + protected abstract fun onUpdate(group: String?, cache: MutableMap) } \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/LocalStorage.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/LocalStorage.kt index 42b1123ef..7aa361af4 100755 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/LocalStorage.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/LocalStorage.kt @@ -7,8 +7,11 @@ import com.itangcent.common.utils.KV import com.itangcent.idea.binder.DbBeanBinderFactory import com.itangcent.idea.plugin.utils.Storage.Companion.DEFAULT_GROUP import com.itangcent.intellij.file.LocalFileRepository -import java.util.* +/** + * Implementation of [Storage] based on local file + * The [LocalStorage] can be accessed cross projects. + */ @Singleton @ScriptTypeName("localStorage") class LocalStorage : AbstractStorage() { @@ -16,115 +19,24 @@ class LocalStorage : AbstractStorage() { @Inject private val localFileRepository: LocalFileRepository? = null - private var dbBeanBinderFactory: DbBeanBinderFactory>? = null - - private fun getDbBeanBinderFactory(): DbBeanBinderFactory> { - if (dbBeanBinderFactory == null) { - synchronized(this) { - dbBeanBinderFactory = DbBeanBinderFactory(localFileRepository!!.getOrCreateFile(".api.local.storage.v1.1.db").path) - { KV.create() } - } - } - return this.dbBeanBinderFactory!! - } - - override fun get(group: String?, name: String?): Any? { - return getDbBeanBinderFactory().getBeanBinder(group ?: DEFAULT_GROUP) - .tryRead()?.get(name) - } - - override fun set(group: String?, name: String?, value: Any?) { - val beanBinder = getDbBeanBinderFactory().getBeanBinder(group ?: DEFAULT_GROUP) - val kv = beanBinder.read() - if (value == null) { - kv.remove(name) - } else { - kv[name] = value - } - beanBinder.save(kv) - } - - override fun pop(group: String?, name: String?): Any? { - val beanBinder = getDbBeanBinderFactory().getBeanBinder(group ?: DEFAULT_GROUP) - val kv = beanBinder.read() - return tryQueue(kv, name)?.let { - val last = it.pollLast() - beanBinder.save(kv) - last - } - } - - override fun peek(group: String?, name: String?): Any? { - val beanBinder = getDbBeanBinderFactory().getBeanBinder(group ?: DEFAULT_GROUP) - val kv = beanBinder.read() - return tryQueue(kv, name)?.peekLast() + private val dbBeanBinderFactory: DbBeanBinderFactory> by lazy { + DbBeanBinderFactory(localFileRepository!!.getOrCreateFile(".api.local.storage.v1.1.db").path) + { KV.create() } } - override fun push(group: String?, name: String?, value: Any?) { - val beanBinder = getDbBeanBinderFactory().getBeanBinder(group ?: DEFAULT_GROUP) - val kv = beanBinder.read() - val queue = queue(kv, name) - queue.addLast(value) - beanBinder.save(kv) + override fun clear(group: String?) { + dbBeanBinderFactory.deleteBinder(group ?: DEFAULT_GROUP) } - @Suppress("UNCHECKED_CAST") - private fun queue(kv: KV, name: String?): LinkedList { - var queue = kv[name] - return when (queue) { - is LinkedList<*> -> { - queue as LinkedList - } - //convert to linkedList - is List<*> -> { - val list = LinkedList(queue) - kv[name] = list - list - } - else -> { - queue = LinkedList() - kv[name ?: Storage.NULL] = queue - queue as LinkedList - } - } + override fun getCache(group: String): MutableMap { + return dbBeanBinderFactory.getBeanBinder(group).tryRead() ?: KV.create() } - - @Suppress("UNCHECKED_CAST") - private fun tryQueue(kv: KV, name: String?): LinkedList? { - val v = kv[name] ?: return null - return when (v) { - is LinkedList<*> -> { - v as LinkedList - } - //convert to linkedList - is List<*> -> { - val list = LinkedList(v) - kv[name] = list - list - } - else -> null + override fun onUpdate(group: String?, cache: MutableMap) { + if (cache.isEmpty()) { + dbBeanBinderFactory.deleteBinder(group ?: DEFAULT_GROUP) + } else { + dbBeanBinderFactory.getBeanBinder(group ?: DEFAULT_GROUP).save(cache as KV) } } - - - override fun remove(group: String?, name: String) { - val beanBinder = getDbBeanBinderFactory().getBeanBinder(group ?: DEFAULT_GROUP) - val kv = beanBinder.tryRead() ?: return - kv.remove(name) - beanBinder.save(kv) - } - - override fun keys(group: String?): Array { - return getDbBeanBinderFactory().getBeanBinder(group ?: DEFAULT_GROUP) - .tryRead() - ?.keys - ?.toTypedArray() - ?: emptyArray() - } - - override fun clear(group: String?) { - getDbBeanBinderFactory().deleteBinder(group ?: DEFAULT_GROUP) - } - } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/SessionStorage.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/SessionStorage.kt index b79b8a9a1..178e207e9 100755 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/SessionStorage.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/SessionStorage.kt @@ -4,66 +4,27 @@ import com.google.inject.Singleton import com.itangcent.annotation.script.ScriptTypeName import com.itangcent.common.utils.KV import com.itangcent.common.utils.sub -import com.itangcent.idea.plugin.utils.Storage.Companion.NULL -import java.util.* +import com.itangcent.idea.plugin.utils.Storage.Companion.DEFAULT_GROUP +/** + * Implementation of [Storage] based on memory + * The [SessionStorage] can only be accessed in the current action. + */ @Singleton @ScriptTypeName("session") class SessionStorage : AbstractStorage() { - private val localKV = ThreadLocal.withInitial { - KV.create() - } - - private val kv: KV - get() = localKV.get() - - override fun get(group: String?, name: String?): Any? { - return kv.sub(group ?: NULL)[name] - } - - override fun set(group: String?, name: String?, value: Any?) { - kv.sub(group ?: NULL)[name ?: NULL] = value - } - - override fun pop(group: String?, name: String?): Any? { - return tryQueue(group, name)?.pollLast() - } - - override fun peek(group: String?, name: String?): Any? { - return tryQueue(group, name)?.peekLast() - } + private val kv: KV by lazy { KV.create() } - override fun push(group: String?, name: String?, value: Any?) { - queue(group, name).addLast(value) + override fun getCache(group: String): MutableMap { + return kv.sub(group) } - @Suppress("UNCHECKED_CAST") - private fun queue(group: String?, name: String?): LinkedList { - val sub = kv.sub(group ?: NULL) - var queue = sub[name ?: NULL] - if (queue == null || queue !is LinkedList<*>) { - queue = LinkedList() - sub[name ?: NULL] = queue + override fun onUpdate(group: String?, cache: MutableMap) { + if (cache.isEmpty()) { + kv.remove(group ?: DEFAULT_GROUP) + } else { + kv[group ?: DEFAULT_GROUP] = cache } - return queue as LinkedList - } - - @Suppress("UNCHECKED_CAST") - private fun tryQueue(group: String?, name: String?): LinkedList? { - val sub = kv.sub(group ?: NULL) - return sub[name ?: NULL] as? LinkedList - } - - override fun remove(group: String?, name: String) { - kv.sub(group ?: NULL).remove(name) - } - - override fun keys(group: String?): Array { - return kv.sub(group ?: NULL).keys.toTypedArray() - } - - override fun clear(group: String?) { - kv.remove(group) } } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/Storage.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/Storage.kt index 64bc79f0d..1b2aae4b8 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/Storage.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/utils/Storage.kt @@ -1,5 +1,8 @@ package com.itangcent.idea.plugin.utils +/** + * The [Storage] provides a way to store data. + */ interface Storage { fun get(name: String?): Any? diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/JacksonUtilsTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/JacksonUtilsTest.kt index 03b140c28..299d8ac30 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/JacksonUtilsTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/utils/JacksonUtilsTest.kt @@ -2,7 +2,6 @@ package com.itangcent.idea.utils import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow /** * Test case of [JacksonUtils] @@ -41,13 +40,8 @@ internal class JacksonUtilsTest { assertEquals("java.lang.Object,[\"java.lang.Object\",{}]", JacksonUtils.toJson(Any())) - assertDoesNotThrow { - JacksonUtils.fromJson("java.lang.Object,[\"java.lang.Object\",{}]").let { - assertNotNull(it) - //todo: make it true - //assertEquals("java.lang.Object", it!!::class.java) - } - } + + assertNotNull(JacksonUtils.fromJson("java.lang.Object,[\"java.lang.Object\",{}]")) } }