未验证 提交 aecb7676 编写于 作者: 梦境迷离's avatar 梦境迷离 提交者: GitHub

Fix (#199)

* support future
上级 da6d0fb7
......@@ -13,18 +13,19 @@ lazy val scala211 = "2.11.12"
lazy val scala213 = "2.13.8"
lazy val lastVersionForExamples = "0.5.2"
lazy val configVersion = "1.4.2"
lazy val scalatestVersion = "3.2.12"
lazy val zioVersion = "1.0.14"
lazy val zioLoggingVersion = "0.5.14"
lazy val caffeineVersion = "2.9.3"
lazy val zioRedisVersion = "0.0.0+381-86c20614-SNAPSHOT" // 实验性质的
lazy val zioSchemaVersion = "0.1.9"
lazy val scalaLoggingVersion = "3.9.5"
lazy val playJsonVersion = "2.7.4"
lazy val log4jVersion = "2.17.2"
lazy val jacksonScalaVersion = "2.13.3"
lazy val jraftVersion = "1.3.9"
lazy val configVersion = "1.4.2"
lazy val scalatestVersion = "3.2.12"
lazy val zioVersion = "1.0.14"
lazy val zioLoggingVersion = "0.5.14"
lazy val caffeineVersion = "2.9.3"
lazy val zioRedisVersion = "0.0.0+381-86c20614-SNAPSHOT" // 实验性质的
lazy val zioSchemaVersion = "0.1.9"
lazy val scalaLoggingVersion = "3.9.5"
lazy val playJsonVersion = "2.7.4"
lazy val log4jVersion = "2.17.2"
lazy val jacksonScalaVersion = "2.13.3"
lazy val jraftVersion = "1.3.9"
lazy val scalaCollectionCompatVersion = "2.7.0"
lazy val commonSettings =
Seq(
......@@ -132,8 +133,9 @@ lazy val `smt-common` = (project in file("smt-common"))
lazy val `smt-cache` = (project in file("smt-cache"))
.settings(commonSettings)
.settings(
name := "smt-cache",
crossScalaVersions := List(scala213, scala212, scala211)
name := "smt-cache",
crossScalaVersions := List(scala213, scala212, scala211),
libraryDependencies += "org.scala-lang.modules" %% "scala-collection-compat" % scalaCollectionCompatVersion
)
.settings(Publishing.publishSettings)
.settings(paradise())
......
......@@ -27,6 +27,7 @@ import org.bitlap.common.{ CaseClassExtractor, CaseClassField }
import java.util.concurrent.atomic.AtomicBoolean
import scala.reflect.ClassTag
import scala.reflect.runtime.universe._
import scala.concurrent.{ ExecutionContext, Future }
/** @author
* 梦境迷离
......@@ -34,30 +35,60 @@ import scala.reflect.runtime.universe._
*/
object Cache {
def getCache[T <: Product](implicit
cache: Aux[String, T],
def getAsyncCache[T <: Product](implicit
cache: Aux[String, T, Future],
executionContext: ExecutionContext,
classTag: ClassTag[T],
typeTag: TypeTag[T]
): CacheRef[String, T] =
new CacheRef[String, cache.Out] {
): CacheRef[String, T, Future] =
new CacheRef[String, cache.Out, Future] {
private lazy val initFlag = new AtomicBoolean(false)
override def init(initKvs: => Map[String, cache.Out]): Unit =
override def init(initKvs: => Map[String, cache.Out]): Future[Unit] =
if (initFlag.compareAndSet(false, true)) {
putTAll(initKvs)
}
} else Future.successful(())
override def putTAll(map: => Map[String, cache.Out]): Unit =
override def putTAll(map: => Map[String, cache.Out]): Future[Unit] =
cache.putAll(map)
override def getT(key: String)(implicit keyBuilder: CacheKeyBuilder[String]): Future[Option[cache.Out]] =
cache.get(key)
override def putT(key: String, value: cache.Out)(implicit keyBuilder: CacheKeyBuilder[String]): Future[Unit] =
cache.put(key, value)
override def getTField(key: String, field: CaseClassField)(implicit
keyBuilder: CacheKeyBuilder[String]
): Future[Option[field.Field]] =
getT(key).map(opt => opt.flatMap(t => CaseClassExtractor.getFieldValueUnSafely[cache.Out](t, field)))
}
def getSyncCache[T <: Product](implicit
cache: Aux[String, T, Identity],
classTag: ClassTag[T],
typeTag: TypeTag[T]
): CacheRef[String, T, Identity] =
new CacheRef[String, cache.Out, Identity] {
private lazy val initFlag = new AtomicBoolean(false)
override def init(initKvs: => Map[String, cache.Out]): Identity[Unit] =
if (initFlag.compareAndSet(false, true)) {
putTAll(initKvs)
} else ()
override def putTAll(map: => Map[String, cache.Out]): Identity[Unit] =
map.foreach(kv => cache.put(kv._1, kv._2))
override def getT(key: String)(implicit keyBuilder: CacheKeyBuilder[String]): Option[cache.Out] = cache.get(key)
override def getT(key: String)(implicit keyBuilder: CacheKeyBuilder[String]): Identity[Option[cache.Out]] =
cache.get(key)
override def putT(key: String, value: cache.Out)(implicit keyBuilder: CacheKeyBuilder[String]): Unit =
override def putT(key: String, value: cache.Out)(implicit keyBuilder: CacheKeyBuilder[String]): Identity[Unit] =
cache.put(key, value)
override def getTField(key: String, field: CaseClassField)(implicit
keyBuilder: CacheKeyBuilder[String]
): Option[field.Field] =
): Identity[Option[field.Field]] =
getT(key).flatMap(t => CaseClassExtractor.getFieldValueUnSafely[cache.Out](t, field))
}
......
......@@ -27,15 +27,15 @@ import org.bitlap.common.CaseClassField
* 梦境迷离
* @version 1.0,6/8/22
*/
trait CacheRef[In, T <: Product] {
trait CacheRef[In, T <: Product, F[_]] {
def init(initKvs: => Map[String, T]): Unit
def init(initKvs: => Map[String, T]): F[Unit]
def putTAll(map: => Map[String, T]): Unit
def putTAll(map: => Map[String, T]): F[Unit]
def getT(key: In)(implicit keyBuilder: CacheKeyBuilder[In]): Option[T]
def getT(key: In)(implicit keyBuilder: CacheKeyBuilder[In]): F[Option[T]]
def putT(key: In, value: T)(implicit keyBuilder: CacheKeyBuilder[String]): Unit
def putT(key: In, value: T)(implicit keyBuilder: CacheKeyBuilder[String]): F[Unit]
def getTField(key: In, field: CaseClassField)(implicit keyBuilder: CacheKeyBuilder[In]): Option[field.Field]
def getTField(key: In, field: CaseClassField)(implicit keyBuilder: CacheKeyBuilder[In]): F[Option[field.Field]]
}
......@@ -25,10 +25,10 @@ package org.bitlap.cache
* 梦境迷离
* @version 1.0,6/8/22
*/
trait CacheType
trait CacheStrategy
object CacheType {
object CacheStrategy {
case class Lru(maxSize: Int = 1000) extends CacheType
case object Normal extends CacheType
case class Lru(maxSize: Int = 1000) extends CacheStrategy
case object Normal extends CacheStrategy
}
......@@ -20,38 +20,85 @@
*/
package org.bitlap.cache
import java.util
import java.util.Collections
import scala.concurrent.{ ExecutionContext, Future }
import scala.jdk.CollectionConverters._
/** @author
* 梦境迷离
* @version 1.0,6/8/22
*/
sealed trait GenericCache[K] {
sealed trait GenericCache[K, F[_]] {
type Out <: Product
def get(key: K)(implicit keyBuilder: CacheKeyBuilder[K]): Option[Out]
def get(key: K)(implicit keyBuilder: CacheKeyBuilder[K]): F[Option[Out]]
def put(key: K, value: Out)(implicit keyBuilder: CacheKeyBuilder[K]): Unit
def put(key: K, value: Out)(implicit keyBuilder: CacheKeyBuilder[K]): F[Unit]
}
def putAll(map: Map[K, Out])(implicit keyBuilder: CacheKeyBuilder[K]): F[Unit]
}
object GenericCache {
type Aux[K, Out0] = GenericCache[K] { type Out = Out0 }
type Aux[K, Out0, F[_]] = GenericCache[K, F] { type Out = Out0 }
def apply[K, Out0 <: Product](cacheType: CacheType): Aux[K, Out0] = new GenericCache[K] {
private val typedCache = cacheType match {
case CacheType.Lru(maxSize) => new java.util.LinkedHashMap[String, Out0](maxSize, 0.75f, true)
case CacheType.Normal => new java.util.LinkedHashMap[String, Out0]()
}
def apply[K, Out0 <: Product](cacheStrategy: CacheStrategy): Aux[K, Out0, Identity] = new GenericCache[K, Identity] {
private val typedCache = getCacheByStrategy[Out0](cacheStrategy)
override type Out = Out0
override def get(key: K)(implicit keyBuilder: CacheKeyBuilder[K]): Option[Out] = {
override def get(
key: K
)(implicit
keyBuilder: CacheKeyBuilder[K]
): Identity[Option[Out]] = {
val v = typedCache.get(keyBuilder.generateKey(key))
if (v == null) None else Option(v)
}
override def put(key: K, value: Out)(implicit keyBuilder: CacheKeyBuilder[K]): Unit =
override def put(key: K, value: Out)(implicit
keyBuilder: CacheKeyBuilder[K]
): Identity[Unit] =
typedCache.put(keyBuilder.generateKey(key), value)
override def putAll(map: Map[K, Out0])(implicit keyBuilder: CacheKeyBuilder[K]): Identity[Unit] =
typedCache.putAll(map.map(kv => keyBuilder.generateKey(kv._1) -> kv._2).asJava)
}
def apply[K, Out0 <: Product](
cacheStrategy: CacheStrategy,
executionContext: ExecutionContext
): Aux[K, Out0, Future] =
new GenericCache[K, Future] {
implicit val ec = executionContext
private val typedCache = getCacheByStrategy[Out0](cacheStrategy)
override type Out = Out0
override def get(key: K)(implicit keyBuilder: CacheKeyBuilder[K]): Future[Option[Out]] =
Future {
val v = typedCache.get(keyBuilder.generateKey(key))
println(s"key => $key | value => $v")
if (v == null) None else Option(v)
}
def put(key: K, value: Out)(implicit keyBuilder: CacheKeyBuilder[K]): Future[Unit] =
Future {
typedCache.put(keyBuilder.generateKey(key), value)
}.map(_ => ())
override def putAll(map: Map[K, Out0])(implicit keyBuilder: CacheKeyBuilder[K]): Future[Unit] =
Future {
println(s"all map => ${map.mkString(" | ")}")
typedCache.putAll(map.map(kv => keyBuilder.generateKey(kv._1) -> kv._2).asJava)
}
}
private def getCacheByStrategy[Out0](cacheType: CacheStrategy): util.Map[String, Out0] =
cacheType match {
case CacheStrategy.Lru(maxSize) =>
Collections.synchronizedMap(new java.util.LinkedHashMap[String, Out0](maxSize, 0.75f, true))
case CacheStrategy.Normal => new java.util.concurrent.ConcurrentHashMap[String, Out0]()
// TODO other cache, redis cache, caffeine cache
}
}
/*
* Copyright (c) 2022 bitlap
*
* 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 org.bitlap
/** @author
* 梦境迷离
* @version 1.0,6/10/22
*/
package object cache {
type Identity[O] = O
}
......@@ -20,10 +20,16 @@
*/
package org.bitlap.cache
import scala.concurrent.ExecutionContext
object CacheImplicits {
implicit lazy val testEntityCache: GenericCache.Aux[String, TestEntity] =
GenericCache[String, TestEntity](CacheType.Normal)
// NOTE:These are two completely different caches and the data is not shared
// If you don't use global, you should also make sure this place the same as getAsyncCache's
implicit lazy val testEntitySyncCache =
GenericCache[String, TestEntity](CacheStrategy.Normal)
implicit lazy val testEntityAsyncCache =
GenericCache[String, TestEntity](CacheStrategy.Normal, ExecutionContext.Implicits.global)
}
......@@ -24,6 +24,9 @@ package org.bitlap.cache
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.bitlap.cache.CacheImplicits._
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt
/** @author
* 梦境迷离
......@@ -37,14 +40,14 @@ class CacheSpec extends AnyFlatSpec with Matchers {
)
"cache1" should "get entity from cache successfully" in {
val cache: CacheRef[String, TestEntity] = Cache.getCache[TestEntity]
val cache = Cache.getSyncCache[TestEntity]
cache.init(data)
val result: Option[TestEntity] = cache.getT("etc")
result shouldBe data.get("etc")
}
"cache2" should "get entity's field from cache successfully" in {
val cache: CacheRef[String, TestEntity] = Cache.getCache[TestEntity]
val cache = Cache.getSyncCache[TestEntity]
cache.init(data)
val result: Option[String] = cache.getTField("etc", TestEntity.key)
result shouldBe Some("world2")
......@@ -54,7 +57,7 @@ class CacheSpec extends AnyFlatSpec with Matchers {
}
"cache3" should "get entity's field after refresh" in {
val cache: CacheRef[String, TestEntity] = Cache.getCache[TestEntity]
val cache = Cache.getSyncCache[TestEntity]
cache.init(data)
val newData = Map(
"btc" -> TestEntity("btc", "hello1", "world1"),
......@@ -66,4 +69,27 @@ class CacheSpec extends AnyFlatSpec with Matchers {
val result: Option[TestEntity] = cache.getT("btc-zh-cn")
result shouldBe newData.get("btc-zh-cn")
}
"cache4" should "async cache" in {
val newData = Map(
"btc" -> TestEntity("btc", "id123", "btc_key123"),
"btc-zh-cn" -> TestEntity("btc", "id123", "btc_zh_key123")
)
val newData2 = Map(
"btc" -> TestEntity("btc", "id456", "bt_key456"),
"btc-zh-cn" -> TestEntity("btc", "id456", "btc_zh_key456"),
"eth" -> TestEntity("btc", "id456", "eth_key456")
)
val cache = Cache.getAsyncCache[TestEntity]
val ret = for {
_ <- cache.init(newData)
btcKey <- cache.getTField("btc", TestEntity.key)
_ <- cache.putTAll(newData2)
ethKey <- cache.getTField("eth", TestEntity.key)
} yield btcKey -> ethKey
Await.result(ret, 3.seconds) shouldBe Option("btc_key123") -> Option("eth_key456")
}
}
......@@ -27,6 +27,7 @@ import scala.reflect.macros.whitebox
import scala.reflect.ClassTag
import scala.reflect.runtime.{ universe => ru }
import scala.reflect.runtime.universe._
import scala.util.{ Failure, Success }
/** @author
* 梦境迷离
......@@ -38,11 +39,17 @@ object CaseClassExtractor {
classTag: ClassTag[T]
): Option[field.Field] = {
val mirror = ru.runtimeMirror(getClass.getClassLoader)
getMethods[T]
.filter(_.name.toTermName.decodedName.toString == field.stringify)
.map(m => mirror.reflect(obj).reflectField(m).get)
.headOption
.map(_.asInstanceOf[field.Field])
val fieldOption = scala.util.Try(
getMethods[T]
.filter(_.name.toTermName.decodedName.toString == field.stringify)
.map(m => mirror.reflect(obj).reflectField(m).get)
.headOption
.map(_.asInstanceOf[field.Field])
)
fieldOption match {
case Success(value) => value
case Failure(exception) => exception.printStackTrace(); None
}
}
def getMethods[T: ru.TypeTag]: List[ru.MethodSymbol] = typeOf[T].members.collect {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册