From 9574dd299bb17f4fc1159d3686f24afcadc861f1 Mon Sep 17 00:00:00 2001 From: Eyre_S Date: Fri, 22 Dec 2023 21:30:03 +0800 Subject: [PATCH] fix EventEnv stackTrack problem, new method for GivenContext --- project/MornyConfiguration.scala | 2 +- .../sukazyo/cono/morny/bot/api/EventEnv.scala | 5 +- .../cono/morny/util/GivenContext.scala | 128 ++++++++++++++---- 3 files changed, 106 insertions(+), 29 deletions(-) diff --git a/project/MornyConfiguration.scala b/project/MornyConfiguration.scala index 0c5052f..041d5f6 100644 --- a/project/MornyConfiguration.scala +++ b/project/MornyConfiguration.scala @@ -8,7 +8,7 @@ object MornyConfiguration { val MORNY_CODE_STORE = "https://github.com/Eyre-S/Coeur-Morny-Cono" val MORNY_COMMIT_PATH = "https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s" - val VERSION = "2.0.0-alpha5" + val VERSION = "2.0.0-alpha6" val VERSION_DELTA: Option[String] = None val CODENAME = "guanggu" diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala index 6d67742..9c37602 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventEnv.scala @@ -6,7 +6,6 @@ import cc.sukazyo.messiva.utils.StackUtils import com.pengrad.telegrambot.model.Update import scala.collection.mutable -import scala.reflect.{classTag, ClassTag} class EventEnv ( @@ -29,11 +28,11 @@ class EventEnv ( //noinspection UnitMethodIsParameterless def setEventOk: Unit = - _status += State.OK(StackUtils.getStackTrace(1)(1)) + _status += State.OK(StackUtils.getStackTrace(1).head) //noinspection UnitMethodIsParameterless def setEventCanceled: Unit = - _status += State.CANCELED(StackUtils.getStackTrace(1)(1)) + _status += State.CANCELED(StackUtils.getStackTrace(1).head) def state: State|Null = _status.lastOption match diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/GivenContext.scala b/src/main/scala/cc/sukazyo/cono/morny/util/GivenContext.scala index 7dbfb19..46172b0 100644 --- a/src/main/scala/cc/sukazyo/cono/morny/util/GivenContext.scala +++ b/src/main/scala/cc/sukazyo/cono/morny/util/GivenContext.scala @@ -1,9 +1,55 @@ package cc.sukazyo.cono.morny.util +import cc.sukazyo.cono.morny.util.GivenContext.ContextNotGivenException + import scala.annotation.targetName import scala.collection.mutable import scala.reflect.{classTag, ClassTag} +object GivenContext { + class ContextNotGivenException extends NoSuchElementException +} + +/** A mutable collection that can store(provide) any typed value and read(use/consume) that value by type. + * + * ## Simple Guide + * {{{ + * val cxt = GivenContext() + * class BaseClass {} + * class MyImplementation extends BaseClass {} + * + * + * cxt.provide(true) // this provides a Boolean + * cxt.provide[BaseClass](new MyImplementation()) // although this object is of type MyImplementation, but it is stored + * // as BaseClass so you can (and actually can only) read it using BaseClass + * cxt << "string" + * cxt << classOf[Int] -> 1 // you can also manually set the stored type using this method + * + * + * cxt >> { (i: Int) => println(i) } || { println("no Int data in the context") } + * val bool = + * cxt.use[String, Boolean] { s => println(s); true } || { false } // when using .use, the return value must declared + * cxt.consume[String] { s => println(s) } // you can use .consume if you don't care the return + * // and this will return a cxt.ConsumeResult[Any] + * val cxtResultOpt = // use toOption if you do not want fallback calculation + * cxt.use[Int, String](int => s"int: $int").toOption // this returns Option[String] + * val cxtResultOpt2 = + * cxt >> { (int: Int) => s"int: $int" } |? // this returns Option[String] too + * // cxt >> { (int: Int) => cxt >> { (str: String) => { str + int } } } |? // this below is not good to use due to .flatUse + * // is not supported yet. It will return a + * // cxt.ConsumeResult[Option[String]] which is very bad + * + * try { // for now, you can use this way to use multiple data + * val int = cxt.use[Int] // this returns CxtOption[Int] which is Either[ContextNotGivenException, Int] + * .toTry.get + * val str = cxt >> classOf[String] match // this >> returns the same with the .use above + * case Right(s) => s + * case Left(err) => throw err // this is ContextNotGivenException + * val bool = cxt >!> classOf[Boolean] // the easier way to do the above + * } catch case e: ContextNotGivenException => // if any of the above val is not available, it will catch the exception + * e.printStackTrace() + * }}} + */ class GivenContext { private type ImplicitsMap [T <: Any] = mutable.HashMap[Class[?], T] @@ -11,19 +57,31 @@ class GivenContext { private val variables: ImplicitsMap[Any] = mutable.HashMap.empty private val variablesWithOwner: ImplicitsMap[ImplicitsMap[Any]] = mutable.HashMap.empty - def provide (i: Any): Unit = - variables += (i.getClass -> i) - def << (i: Any): Unit = - this.provide(i) + def provide [T: ClassTag] (i: T): Unit = + variables += (classTag[T].runtimeClass -> i) + def << [T: ClassTag] (is: (Class[T], T)): Unit = + val (_, i) = is + this.provide[T](i) + def << [T: ClassTag] (i: T): Unit = + this.provide[T](i) - def >>[T: ClassTag] (consumer: T => Unit): ConsumeResult = - this.use[T](consumer) - def use [T: ClassTag] (consumer: T => Unit): ConsumeResult = + private type CxtOption[T] = Either[ContextNotGivenException, T] + def use [T: ClassTag]: CxtOption[T] = variables get classTag[T].runtimeClass match - case Some(i) => consumer(i.asInstanceOf[T]); ConsumeResult(true) - case None => ConsumeResult(false) - def consume [T: ClassTag] (consume: T => Unit): ConsumeResult = - this.use[T](consume) + case Some(i) => Right(i.asInstanceOf[T]) + case None => Left(ContextNotGivenException()) + def use [T: ClassTag, U] (consumer: T => U): ConsumeResult[U] = + this.use[T] match + case Left(_) => ConsumeFailed[U]() + case Right(i) => ConsumeSucceed[U](consumer(i)) + def >> [T: ClassTag] (t: Class[T]): CxtOption[T] = + this.use[T] + def >!> [T: ClassTag] (t: Class[T]): T = + this.use[T].toTry.get + def >>[T: ClassTag, U] (consumer: T => U): ConsumeResult[U] = + this.use[T,U](consumer) + def consume [T: ClassTag] (consume: T => Any): ConsumeResult[Any] = + this.use[T,Any](consume) @targetName("ownedBy") def / [O: ClassTag] (owner: O): OwnedContext[O] = @@ -33,26 +91,46 @@ class GivenContext { class OwnedContext [O: ClassTag] { - def provide (i: Any): Unit = + def provide [T: ClassTag] (i: T): Unit = (variablesWithOwner getOrElseUpdate (classTag[O].runtimeClass, mutable.HashMap.empty)) - .addOne(i.getClass -> i) - def << (i: Any): Unit = - this.provide(i) + .addOne(classTag[T].runtimeClass -> i) + def << [T: ClassTag] (is: (Class[T], T)): Unit = + val (_, i) = is + this.provide[T](i) + def << [T: ClassTag] (i: T): Unit = + this.provide[T](i) - def >> [T: ClassTag] (consumer: T => Unit): ConsumeResult = - this.use[T](consumer) - def use [T: ClassTag] (consumer: T => Unit): ConsumeResult = + def use [T: ClassTag]: CxtOption[T] = variablesWithOwner(classTag[O].runtimeClass) get classTag[T].runtimeClass match - case Some(i) => consumer(i.asInstanceOf[T]); ConsumeResult(true) - case None => ConsumeResult(false) + case Some(i) => Right(i.asInstanceOf[T]) + case None => Left(ContextNotGivenException()) + def use [T: ClassTag, U] (consumer: T => U): ConsumeResult[U] = + use[T] match + case Left(_) => ConsumeFailed[U]() + case Right(i) => ConsumeSucceed[U](consumer(i)) + def >> [T: ClassTag] (t: Class[T]): CxtOption[T] = + this.use[T] + def >!> [T: ClassTag] (t: Class[T]): T = + this.use[T].toTry.get + def >> [T: ClassTag, U] (consumer: T => U): ConsumeResult[U] = + this.use[T,U](consumer) + def consume [T: ClassTag] (consume: T => Any): ConsumeResult[Any] = + this.use[T,Any](consume) } - class ConsumeResult (success: Boolean) { + trait ConsumeResult[U]: + def toOption: Option[U] + def |? : Option[U] = toOption @targetName("orElse") - def || (processor: => Unit): Unit = { - if !success then processor - } - } + def || (processor: =>U): U + private class ConsumeSucceed[U] (succeedValue: U) extends ConsumeResult[U]: + override def toOption: Option[U] = Some(succeedValue) + @targetName("orElse") + override def || (processor: => U): U = succeedValue + private class ConsumeFailed[U] extends ConsumeResult[U]: + override def toOption: Option[U] = None + @targetName("orElse") + override def || (processor: => U): U = processor }