extensions.kproperty.KPropertyExtensions.kt Maven / Gradle / Ivy
/*
* Copyright (c) 2023-Present, Mybatis-Flex-Kotlin ([email protected]).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("unused")
package com.mybatisflex.kotlin.extensions.kproperty
import com.mybatisflex.annotation.Column
import com.mybatisflex.core.query.*
import com.mybatisflex.core.table.TableInfoFactory
import com.mybatisflex.kotlin.extensions.condition.and
import com.mybatisflex.kotlin.extensions.db.queryTable
import com.mybatisflex.kotlin.extensions.db.tableInfo
import com.mybatisflex.kotlin.extensions.sql.*
import com.mybatisflex.kotlin.scope.*
import com.mybatisflex.kotlin.vec.Order
import org.apache.ibatis.type.UnknownTypeHandler
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
import kotlin.reflect.KProperty1
import kotlin.reflect.full.instanceParameter
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.jvmErasure
/*
* KProperty操作扩展
* @author KAMOsama,CloudPlayer
*/
/**
* 根据 [KProperty] 解析 [QueryColumn],用于弥补 Kotlin 中无法直接获取 Java 中的方法引用的问题。
*
* 特指 XXX::getId 这种直接意义上的方法引用。
*
* @author CloudPlayer
* @exception IllegalArgumentException 当此属性没有 backing field 时抛出,或找不到其 [instanceParameter] 时抛出。
* @exception NoSuchElementException 此属性不是枚举类中的属性,返回值类型不在 [TableInfoFactory.defaultSupportColumnTypes] 之中,且其 [Column.typeHandler] 为 [UnknownTypeHandler] 时。
*/
val KProperty<*>.column: QueryColumn
get() = if (this is KProperty0<*>) {
requireNotNull(javaField) {
"Unable to find the corresponding QueryColumn from `$this` without a java field."
}.column
} else {
requireNotNull(instanceParameter) {
"Unable to find the entity class in which property $this is located."
}.type.jvmErasure.tableInfo.getQueryColumnByProperty(name) ?: throwNoSuchElementException()
}
/**
* 同 [KProperty.column] ,但是在没有找到对应 [QueryColumn] 时不会抛出异常,而是返回 null。
*
* @author CloudPlayer
* @see KProperty.column
*/
val KProperty<*>.columnOrNull: QueryColumn?
get() = instanceParameter?.let {
TableInfoFactory.ofEntityClass(it.type.jvmErasure.java).getQueryColumnByProperty(name)
} ?: javaField?.columnOrNull
private fun KProperty<*>.throwNoSuchElementException(): Nothing = requireNotNull(javaField) {
"Unable to find the corresponding QueryColumn from `$this` without a java field."
}.throwNoSuchElementException(toString(), returnType.toString())
/**
* 通过解析 [Column] 以抛出附上详细信息的 [NoSuchElementException] 。
*
* 具体报错原因请见 [KProperty.column] 。
*
* @param from 这个 [Column] 从哪里(字段,属性)来。
* @param typeName 对应的字段或属性的类型签名(即 [Field.getGenericType] 或 [KProperty.returnType])。
* @see KProperty.column
* @author CloudPlayer
*/
private fun Column?.throwNoSuchElementException(from: String, typeName: String): Nothing {
val baseReason = "The corresponding QueryColumn cannot be found by field `$from` because the field's type `$typeName` is an illegal type"
if (this === null) {
throw NoSuchElementException(baseReason)
}
if (ignore) {
throw NoSuchElementException("$baseReason and the value of this property in the `${Column::class.java}` is true.")
}
if (typeHandler.java == UnknownTypeHandler::class.java) {
throw NoSuchElementException("$baseReason and the property of the typeHandler is UnknownTypeHandler.")
}
throw NoSuchElementException(baseReason)
}
/**
* [KProperty1.column] 的内联形式。
*
* @see KProperty1.column
* @author CloudPlayer
*/
inline fun KProperty1.column(): QueryColumn = column(T::class.java)
/**
* 通过 [KProperty1] 来构建 [QueryColumn] ,明确指定类来避免父类继承问题。
*
* @throws NoSuchElementException 找不到对应的 [QueryColumn] 时。具体规则详见 [KProperty.column]。
* @author CloudPlayer
*/
fun KProperty1.column(entityClass: Class): QueryColumn =
TableInfoFactory.ofEntityClass(entityClass).getQueryColumnByProperty(name)
?: throw NoSuchElementException("The attribute $this of class $entityClass could not find the corresponding QueryColumn")
val KClass<*>.defaultColumns: Array
get() = tableInfo.defaultQueryColumn.toTypedArray()
val KClass.allColumns: QueryColumn
get() = QueryColumn(queryTable, "*")
/**
* 通过 [Field] 来构建 [QueryColumn] 。用于在 [KProperty] 是 [KProperty0] 时这种已实例化属性特殊情况下获取声明该属性所在的类。
*
* @author CloudPlayer
* @see KProperty.column
* @throws NoSuchElementException 如果无法通过其获取 [QueryColumn] 。详情请见 [KProperty.column] 。
* @throws IllegalArgumentException 当字段为静态时。
*/
val Field.column: QueryColumn
get() = columnOrNull ?: if (Modifier.isStatic(modifiers)) {
throw IllegalArgumentException("QueryColumn cannot be found via field because field is static.")
} else {
throwNoSuchElementException()
}
/**
* 同 [Field.column] ,但在其是静态或无法获取 [QueryColumn] 时,返回 null。
* @see Field.column
* @author CloudPlayer
*/
val Field.columnOrNull: QueryColumn?
get() = TableInfoFactory.ofEntityClass(declaringClass).getQueryColumnByProperty(name)
private fun Field.throwNoSuchElementException(from: String? = null, typeName: String? = null): Nothing {
getAnnotation(Column::class.java).throwNoSuchElementException(from ?: toGenericString(), typeName ?: genericType.typeName)
}
fun > Array.toQueryColumns(): Array =
map { it.column }.toTypedArray()
fun > Iterable.toQueryColumns(): Array =
map { it.column }.toTypedArray()
// order
fun KProperty.toOrd(order: Order = Order.ASC): QueryOrderBy = column.toOrd(order)
// comparable
infix fun KProperty.eq(other: T): QueryCondition = column.eq(other)
@Deprecated("使用 eq 和 null 进行比较可能是个错误。", ReplaceWith("this.isNull"))
infix fun KProperty.eq(other: Nothing?): QueryCondition = column.eq(other)
infix fun KProperty.eq(other: QueryColumn): QueryCondition = column.eq(other)
infix fun KProperty.eq(other: KProperty): QueryCondition = column.eq(other.column)
infix fun KProperty.ne(other: T): QueryCondition = column.ne(other)
@Deprecated("使用 ne 和 null 进行比较可能是个错误。", ReplaceWith("this.isNotNull"))
infix fun KProperty.ne(other: Nothing?): QueryCondition = column.ne(other)
infix fun KProperty.ne(other: QueryColumn): QueryCondition = column.ne(other)
infix fun KProperty.ne(other: KProperty): QueryCondition = column.ne(other.column)
infix fun > KProperty.gt(other: T): QueryCondition = column.gt(other)
infix fun > KProperty.gt(other: QueryColumn): QueryCondition = column.gt(other)
infix fun > KProperty.gt(other: KProperty): QueryCondition = column.gt(other)
infix fun > KProperty.ge(other: T): QueryCondition = column.ge(other)
infix fun > KProperty.ge(other: QueryColumn): QueryCondition = column.ge(other)
infix fun > KProperty.ge(other: KProperty): QueryCondition = column.ge(other)
infix fun > KProperty.lt(other: T): QueryCondition = column.lt(other)
infix fun > KProperty.lt(other: QueryColumn): QueryCondition = column.lt(other)
infix fun > KProperty.lt(other: KProperty): QueryCondition = column.lt(other)
infix fun > KProperty.le(other: T): QueryCondition = column.le(other)
infix fun > KProperty.le(other: QueryColumn): QueryCondition = column.le(other)
infix fun > KProperty.le(other: KProperty): QueryCondition = column.le(other)
// between
infix fun > KProperty.between(other: ClosedRange): QueryCondition =
column.between(other)
infix fun > KProperty.between(other: Pair): QueryCondition =
column.between(other)
infix fun > KProperty.notBetween(other: ClosedRange): QueryCondition =
column.notBetween(other)
infix fun > KProperty.notBetween(other: Pair): QueryCondition =
column.notBetween(other)
// like
infix fun KProperty.like(other: Any): QueryCondition = column.like(other)
infix fun KProperty.likeRaw(other: Any): QueryCondition = column.likeRaw(other)
infix fun KProperty.likeLeft(other: Any): QueryCondition = column.likeLeft(other)
infix fun KProperty.likeRight(other: Any): QueryCondition = column.likeRight(other)
infix fun KProperty.contains(other: Any): QueryCondition = column.like(other)
infix fun KProperty.startsWith(other: Any): QueryCondition = column.likeLeft(other)
infix fun KProperty.endsWith(other: Any): QueryCondition = column.likeRight(other)
infix fun KProperty.notLike(other: Any): QueryCondition = column.notLike(other)
infix fun KProperty.notLikeRaw(other: Any): QueryCondition = column.notLikeRaw(other)
infix fun KProperty.notLikeLeft(other: Any): QueryCondition = column.notLikeLeft(other)
infix fun KProperty.notLikeRight(other: Any): QueryCondition = column.notLikeRight(other)
// in
/**
* @since 1.1.0
*/
infix fun > KProperty.notIn(value: Collection): QueryCondition = column.notIn(value)
/**
* @since 1.1.0
*/
fun > KProperty.notIn(vararg values:T): QueryCondition = column.notIn(values)
/**
* @since 1.1.0
*/
inline fun > KProperty.notIn(scope: QueryScope.()->Unit): QueryCondition = column.notIn(queryScope(init = scope))
infix fun > KProperty.`in`(queryWrapper: QueryWrapper): QueryCondition = column.`in`(queryWrapper)
infix fun > KProperty.`in`(values: ClosedRange): QueryCondition = this inRange values
infix fun > KProperty.`in`(values: Collection): QueryCondition = this inList values
fun > KProperty.`in`(vararg values: T): QueryCondition = this inArray values
/**
* @since 1.1.0
*/
inline fun KProperty.`in`(scope: QueryScope.()->Unit): QueryCondition = column.`in`(queryScope(init = scope))
fun , E : Comparable> Pair, KProperty>.inPair(vararg others: Pair): QueryCondition =
this inPair others.toList()
infix fun , E : Comparable> Pair, KProperty>.inPair(others: Iterable>): QueryCondition =
others.map { this.first.eq(it.first) and this.second.eq(it.second) }
.reduceIndexed { i, c1, c2 -> (if (i == 1) Brackets(c1) else c1).or(c2) }
fun , B : Comparable, C : Comparable> Pair, KProperty>, KProperty>.inTriple(
vararg others: Pair, C>
): QueryCondition =
this inTriple others.toList()
infix fun , B : Comparable, C : Comparable> Pair, KProperty>, KProperty>.inTriple(
others: Iterable, C>>
): QueryCondition =
others.map { this.first.first.eq(it.first.first) and this.first.second.eq(it.first.second) and this.second.eq(it.second) }
.reduceIndexed { i, c1, c2 -> (if (i == 1) Brackets(c1) else c1).or(c2) }
/**
* @since 1.1.0
*/
infix fun > KProperty.inList(other: Collection): QueryCondition = column.inList(other)
/**
* @since 1.1.0
*/
infix fun > KProperty.inArray(other: Array): QueryCondition = column.inArray(other)
/**
* @since 1.1.0
*/
infix fun > KProperty.inRange(other: ClosedRange): QueryCondition = column.inRange(other)
// as
infix fun KProperty.alias(other: String): QueryColumn = column.`as`(other)
infix fun KProperty.`as`(other: String): QueryColumn = column.`as`(other)
// operator
operator fun KProperty.plus(other: QueryColumn): QueryColumn = column + other
operator fun KProperty.plus(other: KProperty): QueryColumn = column + other.column
operator fun KProperty.plus(other: T): QueryColumn = column + other
operator fun KProperty.minus(other: QueryColumn): QueryColumn = column - other
operator fun KProperty.minus(other: KProperty): QueryColumn = column - other.column
operator fun KProperty.minus(other: T): QueryColumn = column - other
operator fun KProperty.times(other: QueryColumn): QueryColumn = column * other
operator fun KProperty.times(other: KProperty): QueryColumn = column * other.column
operator fun KProperty.times(other: T): QueryColumn = column * other
operator fun KProperty.div(other: QueryColumn): QueryColumn = column / other
operator fun KProperty.div(other: KProperty): QueryColumn = column / other.column
operator fun KProperty.div(other: T): QueryColumn = column / other
operator fun > KProperty.unaryPlus(): QueryOrderBy = this.column.asc()
operator fun > KProperty.unaryMinus(): QueryOrderBy = this.column.desc()
// is
val KProperty<*>.isNull: QueryCondition get() = column.isNull
val KProperty<*>.isNotNull: QueryCondition get() = column.isNotNull