diff --git a/src/main/kotlin/cn/tursom/core/Parser.kt b/src/main/kotlin/cn/tursom/core/Parser.kt index f896207..b89bf1c 100644 --- a/src/main/kotlin/cn/tursom/core/Parser.kt +++ b/src/main/kotlin/cn/tursom/core/Parser.kt @@ -10,75 +10,106 @@ object Parser { fun parse(yaml: Any, clazz: Class): T? { @Suppress("UNCHECKED_CAST") - return if (yaml is List<*> && clazz.isArray) { - parseArray(yaml, clazz) as T - } else when (clazz) { - Any::class.java -> yaml - Int::class.java -> yaml.toInt() - Long::class.java -> yaml.toLong() - Float::class.java -> yaml.toFloat() - Double::class.java -> yaml.toDouble() - Boolean::class.java -> yaml.toBoolean() - - getClazz() -> yaml.toInt() - getClazz() -> yaml.toLong() - getClazz() -> yaml.toFloat() - getClazz() -> yaml.toDouble() - getClazz() -> yaml.toBoolean() - String::class.java -> yaml.toString() - - else -> { - if (yaml !is Map<*, *>) return null - val instance = try { - clazz.newInstance() - } catch (e: Exception) { - unsafe.allocateInstance(clazz) - } - val fields = clazz.declaredFields - fields.forEach { - if ((it.modifiers and (Modifier.STATIC or Modifier.TRANSIENT)) != 0) return@forEach - try { - val parse = parseField(yaml[it.name] ?: return@forEach, it) ?: return@forEach - it.isAccessible = true - it.set(instance, parse) - } catch (e: Exception) { - } - } - instance + return when { + clazz.isInstance(yaml) -> yaml.cast() + clazz.isInheritanceFrom(Enum::class.java) -> try { + val valueOf = clazz.getDeclaredMethod("valueOf", String::class.java) + valueOf.invoke(null, yaml.toString().toUpperCase()) as T + } catch (e: Exception) { + null } - } as T + yaml is List<*> && clazz.isArray -> parseArray(yaml, clazz) as T + else -> when (clazz) { + Any::class.java -> yaml + Int::class.java -> yaml.toInt() + Long::class.java -> yaml.toLong() + Float::class.java -> yaml.toFloat() + Double::class.java -> yaml.toDouble() + Boolean::class.java -> yaml.toBoolean() + + getClazz() -> yaml.toInt() + getClazz() -> yaml.toLong() + getClazz() -> yaml.toFloat() + getClazz() -> yaml.toDouble() + getClazz() -> yaml.toBoolean() + String::class.java -> yaml.toString() + + else -> { + if (yaml !is Map<*, *>) return null + val instance = try { + clazz.newInstance() + } catch (e: Exception) { + unsafe.allocateInstance(clazz) + } + val fields = clazz.declaredFields + fields.forEach { + if ((it.modifiers and (Modifier.STATIC or Modifier.TRANSIENT)) != 0) return@forEach + try { + val parse = parseField(yaml[it.name] ?: return@forEach, it) ?: return@forEach + it.isAccessible = true + it.set(instance, parse) + } catch (e: Exception) { + } + } + instance + } + } as T + } } private fun parseField(yaml: Any, field: Field): Any? { val clazz = field.type @Suppress("UNCHECKED_CAST") - return if (yaml is List<*>) { - when { - clazz.isAssignableFrom(List::class.java) -> { - val type = field.actualTypeArguments - if (type == Any::class.java) { - yaml - } else { - val list = try { - clazz.newInstance() as MutableList - } catch (e: Exception) { - try { - unsafe.allocateInstance(clazz) as MutableList + return when (yaml) { + is List<*> -> { + when { + clazz.isAssignableFrom(List::class.java) -> { + val type = field.actualTypeArguments + if (type == Any::class.java) { + yaml + } else { + val list = try { + clazz.newInstance() as MutableList } catch (e: Exception) { - ArrayList() + try { + unsafe.allocateInstance(clazz) as MutableList + } catch (e: Exception) { + ArrayList() + } } + yaml.forEach { + list.add(parse(it ?: return@forEach, type) ?: return@forEach) + } + list } - yaml.forEach { - list.add(parse(it ?: return@forEach, type) ?: return@forEach) - } - list } + clazz.isAssignableFrom(Set::class.java) -> { + val type = field.actualTypeArguments + if (type == Any::class.java) { + yaml + } else { + val set: MutableSet = try { + clazz.newInstance() as MutableSet + } catch (e: Exception) { + try { + unsafe.allocateInstance(clazz) as MutableSet + } catch (e: Exception) { + HashSet() + } + } + yaml.forEach { + set.add(parse(it ?: return@forEach, type) ?: return@forEach) + } + set + } + } + clazz.isArray -> parseArray(yaml, clazz) + else -> null } - clazz.isArray -> parseArray(yaml, clazz) - else -> null } - } else { - parse(yaml, clazz) + else -> { + parse(yaml, clazz) + } } } diff --git a/utils/mail/src/main/kotlin/cn/tursom/mail/EmailData.kt b/utils/mail/src/main/kotlin/cn/tursom/mail/EmailData.kt index ab2a0a8..9230afa 100644 --- a/utils/mail/src/main/kotlin/cn/tursom/mail/EmailData.kt +++ b/utils/mail/src/main/kotlin/cn/tursom/mail/EmailData.kt @@ -8,7 +8,6 @@ import javax.activation.DataHandler import javax.activation.FileDataSource import javax.mail.Address import javax.mail.Session -import javax.mail.event.TransportEvent import javax.mail.event.TransportListener import javax.mail.internet.InternetAddress import javax.mail.internet.MimeBodyPart diff --git a/utils/mail/src/main/kotlin/cn/tursom/mail/read.kt b/utils/mail/src/main/kotlin/cn/tursom/mail/read.kt new file mode 100644 index 0000000..f7a5841 --- /dev/null +++ b/utils/mail/src/main/kotlin/cn/tursom/mail/read.kt @@ -0,0 +1,120 @@ +package cn.tursom.mail + +import java.nio.charset.Charset +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.ScheduledThreadPoolExecutor +import java.util.concurrent.ThreadFactory +import java.util.concurrent.TimeUnit +import javax.mail.* +import javax.mail.event.MessageCountListener +import javax.mail.internet.InternetAddress + +private val threadPool by lazy { + ScheduledThreadPoolExecutor( + 1, + ThreadFactory { Thread(it, "MailLoop") }) +} + +fun addMailListener( + folder: Folder, + freq: Long, timeUnit: TimeUnit, + listener: MessageCountListener, + newFolder: () -> Folder? = { null } +): ScheduledFuture<*> { + folder.addMessageCountListener(listener) + var mailFOlder = folder + return threadPool.scheduleAtFixedRate({ + try { + mailFOlder.messageCount + } catch (e: FolderClosedException) { + mailFOlder = newFolder() ?: throw e + mailFOlder.addMessageCountListener(listener) + } catch (e: Exception) { + e.printStackTrace() + throw e + } + }, 0, freq, timeUnit) +} + +fun addMailListener( + newFolder: () -> Folder, + freq: Long, timeUnit: TimeUnit, + listener: MessageCountListener +): ScheduledFuture<*> = addMailListener(newFolder(), freq, timeUnit, listener, newFolder) + +fun getStore(host: String, port: Int, account: String, password: String): Store { + val props = System.getProperties() + props["mail.imap.host"] = host + props["mail.imap.port"] = port + props["mail.imap.auth"] = "true" + props["mail.imap.ssl.enable"] = "true" + props["mail.imap.socketFactory.port"] = port + props["mail.imap.socketFactory.fallback"] = "false" + props["mail.imap.socketFactory.class"] = "javax.net.ssl.SSLSocketFactory" + val session = Session.getDefaultInstance(props) + val store = session.getStore("imap") + store.connect(account, password) + return store +} + +fun getFolder(host: String, port: Int, account: String, password: String): Pair { + val store: Store = getStore(host, port, account, password) + val folder: Folder = store.getFolder("INBOX") + folder.open(Folder.READ_ONLY) + return store to folder +} + +fun forEachMail(host: String, port: Int, account: String, password: String, onMsg: (msg: Message) -> Unit) { + val (store, folder) = getFolder(host, port, account, password) + // 全部邮件数 + val messages: Array = folder.messages + messages.forEach(onMsg) + folder.close(true) + store.close() +} + +fun getText(message: Message): String? = when (val content: Any = message.content) { + is String -> content + is Multipart -> { + val sb = StringBuilder() + repeat(content.count) { + val part: Part = content.getBodyPart(it) + if (part.contentType.contains("text/plain")) { + var charsetStart = part.contentType.indexOf("charset=", 0, true) + if (charsetStart != -1) charsetStart += "charset=".length + var charsetEnd = part.contentType.indexOf(';', charsetStart) + if (charsetEnd == -1) { + charsetEnd = part.contentType.length + } + val msg = part.inputStream.bufferedReader( + if (charsetStart == -1) { + Charsets.UTF_8 + } else { + Charset.forName(part.contentType.substring(charsetStart, charsetEnd)) + } + ).readText() + sb.append(msg) + } + } + if (sb.isEmpty()) { + null + } else { + sb.toString() + } + } + else -> null +} + +fun forEachUnreadMail(host: String, port: Int, account: String, password: String, onMsg: (from: InternetAddress, msg: String) -> Boolean) { + forEachMail(host, port, account, password) { message -> + // 跳过已读邮件 + if (message.flags.contains(Flags.Flag.SEEN)) return@forEachMail + val msg = getText(message) + val read = when (val text = getText(message)) { + null -> false + else -> onMsg(message.from[0] as InternetAddress, text) + } + //未读邮件标记为已读 + message.setFlag(Flags.Flag.SEEN, read) + } +} diff --git a/utils/yaml/src/main/kotlin/cn/tursom/yaml/Yaml.kt b/utils/yaml/src/main/kotlin/cn/tursom/yaml/Yaml.kt index 22952da..23f7e8e 100644 --- a/utils/yaml/src/main/kotlin/cn/tursom/yaml/Yaml.kt +++ b/utils/yaml/src/main/kotlin/cn/tursom/yaml/Yaml.kt @@ -16,57 +16,78 @@ object Yaml { return stringBuilder.toString() } - private fun toYaml(obj: Any, stringBuilder: StringBuilder, indentation: String) { + private fun toYaml(obj: Any, stringBuilder: StringBuilder, indentation: String, inCollection: Boolean = false) { when (obj) { - is Byte -> stringBuilder.append("$obj") - is Char -> stringBuilder.append("$obj") - is Short -> stringBuilder.append("$obj") - is Int -> stringBuilder.append("$obj") - is Long -> stringBuilder.append("$obj") - is Float -> stringBuilder.append("$obj") - is Double -> stringBuilder.append("$obj") - is String -> stringBuilder.append("$obj") - is Collection<*> -> obj.forEach { - it ?: return@forEach - stringBuilder.append("$indentation- ") - toYaml(it, stringBuilder, "$indentation ") - if (!stringBuilder.endsWith('\n')) { - stringBuilder.append("\n") + is Byte -> stringBuilder.append(obj) + is Char -> stringBuilder.append(obj) + is Short -> stringBuilder.append(obj) + is Int -> stringBuilder.append(obj) + is Long -> stringBuilder.append(obj) + is Float -> stringBuilder.append(obj) + is Double -> stringBuilder.append(obj) + is String -> stringBuilder.append(obj) + is Map<*, *> -> { + var first = true + obj.forEach { (any, u) -> + if (inCollection && first) { + stringBuilder.append("${any ?: return@forEach}: ") + first = false + } else { + stringBuilder.append("$indentation${any ?: return@forEach}: ") + } + toYaml(u ?: return@forEach, stringBuilder, "$indentation ") + if (!stringBuilder.endsWith('\n')) { + stringBuilder.append("\n") + } } } - is Map<*, *> -> obj.forEach { (any, u) -> - stringBuilder.append("$indentation${any ?: return@forEach}: ") - toYaml(u ?: return@forEach, stringBuilder, "$indentation ") - if (!stringBuilder.endsWith('\n')) { - stringBuilder.append("\n") + is Collection<*> -> if (obj.isEmpty()) { + stringBuilder.append("[]") + } else { + var appended = 0 + obj.forEach { + it ?: return@forEach + stringBuilder.append("${if (appended == 0) '\n' else ""}$indentation- ") + appended++ + toYaml(it, stringBuilder, "$indentation ", true) + if (!stringBuilder.endsWith('\n')) { + stringBuilder.append("\n") + } } } else -> { + var first = true + fun getIndentation() = if (inCollection && first) { + first = false + "" + } else { + indentation + } obj.javaClass.declaredFields.forEach { if ((it.modifiers and (Modifier.STATIC or Modifier.TRANSIENT)) != 0) return@forEach it.isAccessible = true val value = it.get(obj) when (it.type) { - Byte::class.java -> stringBuilder.append("$indentation${it.name}: $value\n") - Char::class.java -> stringBuilder.append("$indentation${it.name}: $value\n") - Short::class.java -> stringBuilder.append("$indentation${it.name}: $value\n") - Int::class.java -> stringBuilder.append("$indentation${it.name}: $value\n") - Long::class.java -> stringBuilder.append("$indentation${it.name}: $value\n") - Float::class.java -> stringBuilder.append("$indentation${it.name}: $value\n") - Double::class.java -> stringBuilder.append("$indentation${it.name}: $value\n") + Byte::class.java -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + Char::class.java -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + Short::class.java -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + Int::class.java -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + Long::class.java -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + Float::class.java -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + Double::class.java -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") - getClazz() -> stringBuilder.append("$indentation${it.name}: $value\n") - getClazz() -> stringBuilder.append("$indentation${it.name}: $value\n") - getClazz() -> stringBuilder.append("$indentation${it.name}: $value\n") - getClazz() -> stringBuilder.append("$indentation${it.name}: $value\n") - getClazz() -> stringBuilder.append("$indentation${it.name}: $value\n") - getClazz() -> stringBuilder.append("$indentation${it.name}: $value\n") - getClazz() -> stringBuilder.append("$indentation${it.name}: $value\n") - String::class.java -> stringBuilder.append("$indentation${it.name}: $value\n") + getClazz() -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + getClazz() -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + getClazz() -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + getClazz() -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + getClazz() -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + getClazz() -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + getClazz() -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") + String::class.java -> stringBuilder.append("${getIndentation()}${it.name}: $value\n") else -> { - stringBuilder.append("$indentation${it.name}:\n") - toYaml(value, stringBuilder, "$indentation ") + stringBuilder.append("${getIndentation()}${it.name}: ") + toYaml(value, stringBuilder, "${getIndentation()} ") } } } @@ -81,7 +102,6 @@ object Yaml { inline fun parseResource(classLoader: ClassLoader, path: String) = parseResource(classLoader, path, T::class.java) fun parseResource(path: String, clazz: Class) = parseResource(this.javaClass.classLoader, path, clazz) - fun parseResource(classLoader: ClassLoader, path: String, clazz: Class) = Parser.parse(yaml.load(classLoader.getResourceAsStream(path)), clazz) - fun parse(yaml: String, clazz: Class) = Parser.parse(this.yaml.load(yaml), clazz) - + fun parseResource(classLoader: ClassLoader, path: String, clazz: Class) = Parser.parse(yaml.load(classLoader.getResourceAsStream(path)), clazz) + fun parse(yaml: String, clazz: Class) = Parser.parse(this.yaml.load(yaml), clazz) } \ No newline at end of file