diff --git a/gradle.properties b/gradle.properties
index 3d87307..d37e7e7 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,7 +8,7 @@ MORNY_COMMIT_PATH = https://github.com/Eyre-S/Coeur-Morny-Cono/commit/%s
VERSION = 1.0.0-RC5
USE_DELTA = true
-VERSION_DELTA = scala3
+VERSION_DELTA = scala4
CODENAME = beiping
diff --git a/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala b/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
index 7df72aa..9d0f2ba 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/MornyCoeur.scala
@@ -31,10 +31,12 @@ class MornyCoeur (using val config: MornyConfig) {
if config.telegramBotUsername ne null then
logger info s"login as:\n ${config.telegramBotUsername}"
- private val __loginResult = login()
- if (__loginResult eq null)
- logger error "Login to bot failed."
- System exit -1
+ private val __loginResult: LoginResult = login() match
+ case some: Some[LoginResult] => some.get
+ case None =>
+ logger error "Login to bot failed."
+ System exit -1
+ throw RuntimeException()
configure_exitCleanup()
@@ -63,8 +65,8 @@ class MornyCoeur (using val config: MornyConfig) {
val events: MornyEventListeners = MornyEventListeners(using eventManager)
/** inner value: about why morny exit, used in [[daemon.MornyReport]]. */
- private var whileExit_reason: AnyRef|Null = _
- def exitReason: AnyRef|Null = whileExit_reason
+ private var whileExit_reason: Option[AnyRef] = None
+ def exitReason: Option[AnyRef] = whileExit_reason
val coeurStartTimestamp: Long = ServerMain.systemStartupTime
///>>> BLOCK START instance configure & startup stage 2
@@ -101,12 +103,12 @@ class MornyCoeur (using val config: MornyConfig) {
}
def exit (status: Int, reason: AnyRef): Unit =
- whileExit_reason = reason
+ whileExit_reason = Some(reason)
System exit status
private case class LoginResult(account: TelegramBot, username: String, userid: Long)
- private def login (): LoginResult|Null = {
+ private def login (): Option[LoginResult] = {
val builder = TelegramBot.Builder(config.telegramBotKey)
var api_bot = config.telegramBotApiServer
@@ -129,7 +131,7 @@ class MornyCoeur (using val config: MornyConfig) {
val account = builder build
logger info "Trying to login..."
- boundary[LoginResult|Null] {
+ boundary[Option[LoginResult]] {
for (i <- 0 to 3) {
if i > 0 then logger info "retrying..."
try {
@@ -137,16 +139,16 @@ class MornyCoeur (using val config: MornyConfig) {
if ((config.telegramBotUsername ne null) && config.telegramBotUsername != remote.username)
throw RuntimeException(s"Required the bot @${config.telegramBotUsername} but @${remote.username} logged in")
logger info s"Succeed logged in to @${remote.username}"
- break(LoginResult(account, remote.username, remote.id))
+ break(Some(LoginResult(account, remote.username, remote.id)))
} catch
- case r: boundary.Break[LoginResult|Null] => throw r
+ case r: boundary.Break[Option[LoginResult]] => throw r
case e =>
logger error
s"""${exceptionLog(e)}
|login failed"""
.stripMargin
}
- null
+ None
}
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala
index 935f561..5448204 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/api/EventListenerManager.scala
@@ -10,6 +10,12 @@ import com.pengrad.telegrambot.UpdatesListener
import scala.collection.mutable
import scala.language.postfixOps
+/** Contains a [[mutable.Queue]] of [[EventListener]], and delivery telegram [[Update]].
+ *
+ * Implemented [[process]] in [[UpdatesListener]] so it can directly used in [[com.pengrad.telegrambot.TelegramBot.setupListener]].
+ *
+ * @param coeur the [[MornyCoeur]] context.
+ */
class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
private val listeners = mutable.Queue.empty[EventListener]
@@ -77,9 +83,19 @@ class EventListenerManager (using coeur: MornyCoeur) extends UpdatesListener {
}
+
import java.util
import scala.jdk.CollectionConverters.*
-
+ /** Delivery the telegram [[Update]]s.
+ *
+ * The implementation of [[UpdatesListener]].
+ *
+ * For each [[Update]], create an [[EventRunner]] for it, and
+ * start the it.
+ *
+ * @return [[UpdatesListener.CONFIRMED_UPDATES_ALL]], for all Updates
+ * should be processed in [[EventRunner]] created for it.
+ */
override def process (updates: util.List[Update]): Int = {
for (update <- updates.asScala)
EventRunner(using update).start()
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/ICommandAlias.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/ICommandAlias.scala
index 749f550..de9dbe7 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/ICommandAlias.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/ICommandAlias.scala
@@ -1,17 +1,43 @@
package cc.sukazyo.cono.morny.bot.command
+/** One alias definition, contains the necessary message of how
+ * to process the alias.
+ */
trait ICommandAlias {
+ /** The alias name.
+ *
+ * same with the command name, it is the unique identifier of this alias.
+ */
val name: String
+ /** If the alias should be listed while list commands to end-user.
+ *
+ * The alias can only be listed when the parent command can be listed
+ * (meanwhile the parent command implemented [[ITelegramCommand]]). If the
+ * parent command cannot be listed, it will always cannot be listed.
+ */
val listed: Boolean
}
+/** Default implementations of [[ICommandAlias]]. */
object ICommandAlias {
+ /** Alias which can be listed to end-user.
+ *
+ * the [[ICommandAlias.listed]] value is always true.
+ *
+ * @param name The alias name, see more in [[ICommandAlias.name]]
+ */
case class ListedAlias (name: String) extends ICommandAlias:
override val listed = true
+ /** Alias which cannot be listed to end-user.
+ *
+ * the [[ICommandAlias.listed]] value is always false.
+ *
+ * @param name The alias name, see more in [[ICommandAlias.name]]
+ */
case class HiddenAlias (name: String) extends ICommandAlias:
override val listed = false
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/ISimpleCommand.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/ISimpleCommand.scala
index 25181bd..24c624f 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/ISimpleCommand.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/ISimpleCommand.scala
@@ -3,11 +3,37 @@ package cc.sukazyo.cono.morny.bot.command
import cc.sukazyo.cono.morny.util.tgapi.InputCommand
import com.pengrad.telegrambot.model.Update
+/** A simple command.
+ *
+ * Contains only [[name]] and [[aliases]].
+ *
+ * Won't be listed to end-user. if you want the command listed,
+ * see [[ITelegramCommand]].
+ *
+ */
trait ISimpleCommand {
+ /** the main name of the command.
+ *
+ * must have a value as the unique identifier of this command.
+ */
val name: String
+ /** aliases of the command.
+ *
+ * Alias means it is the same to call [[name main name]] when call this.
+ * There can be multiple aliases. But notice that, although alias is not
+ * the unique identifier, it uses the same namespace with [[name]], means
+ * it also cannot be duplicate with other [[name]] or [[aliases]].
+ *
+ * It can be [[Null]], means no aliases.
+ */
val aliases: Array[ICommandAlias]|Null
+ /** The work code of this command.
+ *
+ * @param command The parsed input command which called this command.
+ * @param event The raw event which called this command.
+ */
def execute (using command: InputCommand, event: Update): Unit
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/ITelegramCommand.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/ITelegramCommand.scala
index 2c16263..1a720b8 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/ITelegramCommand.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/ITelegramCommand.scala
@@ -1,8 +1,25 @@
package cc.sukazyo.cono.morny.bot.command
+/** A complex telegram command.
+ *
+ * the extension of [[ISimpleCommand]], with external defines of the necessary
+ * introduction message ([[paramRule]] and [[description]]).
+ *
+ * It can be listed to end-user.
+ */
trait ITelegramCommand extends ISimpleCommand {
+ /** The param rule of this command, used in human-readable command list.
+ *
+ * The param rule uses a symbol language to describe how this command
+ * receives paras.
+ *
+ * Set it empty to make this scope not available.
+ */
val paramRule: String
+ /** The description/introduction of this command, used in human-readable
+ * command list.
+ */
val description: String
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyInformation.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyInformation.scala
index be4aafc..36222eb 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyInformation.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/MornyInformation.scala
@@ -122,7 +122,7 @@ class MornyInformation (using coeur: MornyCoeur) extends ITelegramCommand {
event.message.chat.id,
/* language=html */
s"""system:
- |- ${h(if (getRuntimeHostname == null) "" else getRuntimeHostname)}
+ |- ${h(if getRuntimeHostname nonEmpty then getRuntimeHostname.get else "")}
|- ${h(sysprop("os.name"))}
${h(sysprop("os.arch"))}
${h(sysprop("os.version"))}
|java runtime:
|- ${h(sysprop("java.vm.vendor"))}.${h(sysprop("java.vm.name"))}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/command/Nbnhhsh.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/command/Nbnhhsh.scala
index 351eb58..543a783 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/command/Nbnhhsh.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/command/Nbnhhsh.scala
@@ -25,19 +25,17 @@ class Nbnhhsh (using coeur: MornyCoeur) extends ITelegramCommand {
override def execute (using command: InputCommand, event: Update): Unit = {
- val queryTarget: String|Null =
+ val queryTarget: String =
if command.args nonEmpty then
command.args mkString " "
else if (event.message.replyToMessage != null && event.message.replyToMessage.text != null)
event.message.replyToMessage.text
- else null
-
- if (queryTarget == null)
- coeur.account exec SendSticker(
- event.message.chat.id,
- TelegramStickers ID_404
- ).replyToMessageId(event.message.messageId)
- return;
+ else
+ coeur.account exec SendSticker(
+ event.message.chat.id,
+ TelegramStickers ID_404
+ ).replyToMessageId(event.message.messageId)
+ return;
try {
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala
index 9c2c80c..db920c0 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnCallMsgSend.scala
@@ -17,11 +17,11 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
private val REGEX_MSG_SENDREQ_DATA_HEAD: Regex = "^\\*msg(-?\\d+)(\\*\\S+)?(?:\\n([\\s\\S]+))?$"r
case class MessageToSend (
- message: String|Null,
- entities: Array[MessageEntity]|Null,
- parseMode: ParseMode|Null,
- targetId: Long
- ) {
+ message: String|Null,
+ entities: Array[MessageEntity]|Null,
+ parseMode: ParseMode|Null,
+ targetId: Long
+ ) {
def toSendMessage (target_override: Long|Null = null): SendMessage =
val useTarget = if target_override == null then targetId else target_override
val sendMessage = SendMessage(useTarget, message)
@@ -129,8 +129,8 @@ class OnCallMsgSend (using coeur: MornyCoeur) extends EventListener {
}
if messageToSend.message eq null then return true
- val testSendResponse = coeur.account execute messageToSend.toSendMessage(update.message.chat.id)
- .replyToMessageId(update.message.messageId)
+ val testSendResponse = coeur.account execute
+ messageToSend.toSendMessage(update.message.chat.id).replyToMessageId(update.message.messageId)
if (!(testSendResponse isOk))
coeur.account exec SendMessage(
update.message.chat.id,
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala
index 9f397a7..1aff5f2 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/event/OnQuestionMarkReply.scala
@@ -8,6 +8,7 @@ import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.request.SendMessage
import scala.language.postfixOps
+import scala.util.boundary
class OnQuestionMarkReply (using coeur: MornyCoeur) extends EventListener {
@@ -34,10 +35,10 @@ object OnQuestionMarkReply {
private val QUESTION_MARKS = Set('?', '?', '¿', '⁈', '⁇', '‽', '❔', '❓')
def isAllMessageMark (using text: String): Boolean = {
- var isAll = true
- for (c <- text)
- if !(QUESTION_MARKS contains c) then isAll = false
- isAll
+ boundary[Boolean] {
+ for (c <- text) if QUESTION_MARKS contains c then boundary.break(false)
+ true
+ }
}
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala
index a74aad2..f73fed6 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolBilibili.scala
@@ -3,6 +3,7 @@ package cc.sukazyo.cono.morny.bot.query
import cc.sukazyo.cono.morny.Log.logger
import cc.sukazyo.cono.morny.util.tgapi.formatting.NamingUtils.inlineQueryId
import cc.sukazyo.cono.morny.util.BiliTool
+import cc.sukazyo.cono.morny.util.UseSelect.select
import com.pengrad.telegrambot.model.Update
import com.pengrad.telegrambot.model.request.{InlineQueryResultArticle, InputTextMessageContent, ParseMode}
@@ -24,23 +25,23 @@ class ShareToolBilibili extends ITelegramQuery {
if (event.inlineQuery.query == null) return null
event.inlineQuery.query match
- case REGEX_BILI_VIDEO(_1, _2, _3, _4, _5, _6, _7) =>
+ case REGEX_BILI_VIDEO(_url_v, _url_av, _url_bv, _url_param, _url_v_part, _raw_av, _raw_bv) =>
logger debug
s"""====== Share Tool Bilibili Catch ok
- |1: ${_1}
- |2: ${_2}
- |3: ${_3}
- |4: ${_4}
- |5: ${_5}
- |6: ${_6}
- |7: ${_7}"""
+ |1: ${_url_v}
+ |2: ${_url_av}
+ |3: ${_url_bv}
+ |4: ${_url_param}
+ |5: ${_url_v_part}
+ |6: ${_raw_av}
+ |7: ${_raw_bv}"""
.stripMargin
- var av = if (_2 != null) _2 else if (_6 != null) _6 else null
- var bv = if (_3!=null) _3 else if (_7!=null) _7 else null
+ var av = select(_url_av, _raw_av)
+ var bv = select(_url_bv, _raw_bv)
logger trace s"catch id av[$av] bv[$bv]"
- val part: Int|Null = if (_5!=null) _5 toInt else null
+ val part: Int|Null = if (_url_v_part!=null) _url_v_part toInt else null
logger trace s"catch video part[$part]"
if (av == null) {
diff --git a/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolTwitter.scala b/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolTwitter.scala
index 03ba0de..a550501 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolTwitter.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/bot/query/ShareToolTwitter.scala
@@ -21,15 +21,15 @@ class ShareToolTwitter extends ITelegramQuery {
event.inlineQuery.query match
- case REGEX_TWEET_LINK(_, _2, _, _, _, _) =>
+ case REGEX_TWEET_LINK(_, _path_data, _, _, _, _) =>
List(
InlineQueryUnit(InlineQueryResultArticle(
inlineQueryId(ID_PREFIX_VX+event.inlineQuery.query), TITLE_VX,
- s"https://vxtwitter.com/$_2"
+ s"https://vxtwitter.com/$_path_data"
)),
InlineQueryUnit(InlineQueryResultArticle(
inlineQueryId(ID_PREFIX_VX_COMBINED+event.inlineQuery.query), TITLE_VX_COMBINED,
- s"https://c.vxtwitter.com/$_2"
+ s"https://c.vxtwitter.com/$_path_data"
))
)
diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala
index df11781..f01671a 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MedicationTimer.scala
@@ -24,7 +24,7 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread {
this.setName(DAEMON_THREAD_NAME_DEF)
- private var lastNotify_messageId: Int|Null = _
+ private var lastNotify_messageId: Option[Int] = None
override def run (): Unit = {
logger info "Medication Timer started."
@@ -51,8 +51,8 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread {
private def sendNotification(): Unit = {
val sendResponse: SendResponse = coeur.account exec SendMessage(notify_toChat, NOTIFY_MESSAGE)
- if sendResponse isOk then lastNotify_messageId = sendResponse.message.messageId
- else lastNotify_messageId = null
+ if sendResponse isOk then lastNotify_messageId = Some(sendResponse.message.messageId)
+ else lastNotify_messageId = None
}
@throws[InterruptedException | IllegalArgumentException]
@@ -61,7 +61,7 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread {
}
def refreshNotificationWrite (edited: Message): Unit = {
- if lastNotify_messageId != (edited.messageId toInt) then return
+ if (lastNotify_messageId isEmpty) || (lastNotify_messageId.get != (edited.messageId toInt)) then return
import cc.sukazyo.cono.morny.util.CommonFormat.formatDate
val editTime = formatDate(edited.editDate*1000, use_timeZone.getTotalSeconds/60/60)
val entities = ArrayBuffer.empty[MessageEntity]
@@ -72,7 +72,7 @@ class MedicationTimer (using coeur: MornyCoeur) extends Thread {
edited.messageId,
edited.text + s"\n-- $editTime --"
).entities(entities toArray:_*)
- lastNotify_messageId = null
+ lastNotify_messageId = None
}
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala
index 9461880..3528ff1 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/daemon/MornyReport.scala
@@ -101,9 +101,9 @@ class MornyReport (using coeur: MornyCoeur) {
def reportCoeurExit (): Unit = {
val causedTag = coeur.exitReason match
- case u: User => u.fullnameRefHTML
- case n if n == null => "UNKNOWN reason"
- case a: AnyRef => /*language=html*/ s"${h(a.toString)}
"
+ case None => "UNKNOWN reason"
+ case u: Some[User] => u.get.fullnameRefHTML
+ case a: Some[_] => /*language=html*/ s"${h(a.get.toString)}
"
executeReport(SendMessage(
coeur.config.reportToChat,
// language=html
diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/MornyInformation.scala b/src/main/scala/cc/sukazyo/cono/morny/data/MornyInformation.scala
index 12e7671..19e4642 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/data/MornyInformation.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/data/MornyInformation.scala
@@ -29,9 +29,9 @@ object MornyInformation {
}
//noinspection ScalaWeakerAccess
- def getRuntimeHostname: String | Null = {
- try InetAddress.getLocalHost.getHostName
- catch case _: UnknownHostException => null
+ def getRuntimeHostname: Option[String] = {
+ try Some(InetAddress.getLocalHost.getHostName)
+ catch case _: UnknownHostException => None
}
def getAboutPic: Array[Byte] = TelegramImages.IMG_ABOUT get
diff --git a/src/main/scala/cc/sukazyo/cono/morny/data/TelegramImages.scala b/src/main/scala/cc/sukazyo/cono/morny/data/TelegramImages.scala
index 6fb7757..ac45039 100644
--- a/src/main/scala/cc/sukazyo/cono/morny/data/TelegramImages.scala
+++ b/src/main/scala/cc/sukazyo/cono/morny/data/TelegramImages.scala
@@ -13,17 +13,17 @@ object TelegramImages {
class AssetsFileImage (assetsPath: String) {
- private var cache: Array[Byte]|Null = _
+ private var cache: Option[Array[Byte]] = None
@throws[AssetsException]
def get:Array[Byte] =
- if cache eq null then read()
- cache
+ if cache isEmpty then read()
+ cache.get
@throws[AssetsException]
private def read (): Unit = {
Using ((MornyAssets.pack getResource assetsPath)read) { stream =>
- try { this.cache = stream.readAllBytes() }
+ try { this.cache = Some(stream.readAllBytes()) }
catch case e: IOException => {
throw AssetsException(e)
}
diff --git a/src/main/scala/cc/sukazyo/cono/morny/util/UseSelect.scala b/src/main/scala/cc/sukazyo/cono/morny/util/UseSelect.scala
new file mode 100644
index 0000000..feb3e99
--- /dev/null
+++ b/src/main/scala/cc/sukazyo/cono/morny/util/UseSelect.scala
@@ -0,0 +1,27 @@
+package cc.sukazyo.cono.morny.util
+
+import scala.util.boundary
+
+/** Useful utils of select one specific value in the given values.
+ *
+ * contains:
+ * - [[select()]] can select one value which is not [[Null]].
+ *
+ */
+object UseSelect {
+
+ /** Select the non-null value in the given values.
+ *
+ * @tparam T The value's type.
+ * @param values Given values, may be a T value or [[Null]].
+ * @return The first non-null value in the given values, or [[Null]] if
+ * there's no non-null value.
+ */
+ def select [T] (values: T|Null*): T|Null = {
+ boundary[T|Null] {
+ for (i <- values) if i != null then boundary.break(i)
+ null
+ }
+ }
+
+}