mirror of
https://github.com/szkolny-eu/szkolny-android.git
synced 2025-01-18 12:56:45 -06:00
[Events] Add support for selective updates and upserting.
This commit is contained in:
parent
2f3c912dbe
commit
0427fa6087
1
annotation/.gitignore
vendored
Normal file
1
annotation/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
29
annotation/build.gradle
Normal file
29
annotation/build.gradle
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-28.
|
||||||
|
*/
|
||||||
|
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceCompatibility = "7"
|
||||||
|
targetCompatibility = "7"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
compileKotlin {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileTestKotlin {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-28.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.annotation
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@MustBeDocumented
|
||||||
|
annotation class SelectiveDao(
|
||||||
|
val db: KClass<*>
|
||||||
|
)
|
@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-28.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.annotation
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@MustBeDocumented
|
||||||
|
annotation class UpdateSelective(
|
||||||
|
val primaryKeys: Array<String>,
|
||||||
|
val skippedColumns: Array<String> = []
|
||||||
|
)
|
@ -193,6 +193,9 @@ dependencies {
|
|||||||
implementation "io.coil-kt:coil:0.9.2"
|
implementation "io.coil-kt:coil:0.9.2"
|
||||||
|
|
||||||
implementation 'com.github.kuba2k2:NumberSlidingPicker:2921225f76'
|
implementation 'com.github.kuba2k2:NumberSlidingPicker:2921225f76'
|
||||||
|
|
||||||
|
implementation project(":annotation")
|
||||||
|
kapt project(":codegen")
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -284,7 +284,7 @@ abstract class Data(val app: App, val profile: Profile?, val loginStore: LoginSt
|
|||||||
db.gradeDao().addAll(gradeList)
|
db.gradeDao().addAll(gradeList)
|
||||||
}
|
}
|
||||||
if (eventList.isNotEmpty()) {
|
if (eventList.isNotEmpty()) {
|
||||||
db.eventDao().addAll(eventList)
|
db.eventDao().upsertAll(eventList)
|
||||||
}
|
}
|
||||||
if (noticeList.isNotEmpty()) {
|
if (noticeList.isNotEmpty()) {
|
||||||
db.noticeDao().clear(profile.id)
|
db.noticeDao().clear(profile.id)
|
||||||
|
@ -44,7 +44,7 @@ class AppSync(val app: App, val notifications: MutableList<Notification>, val pr
|
|||||||
event.addedDate
|
event.addedDate
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return app.db.eventDao().addAll(events).size
|
return app.db.eventDao().upsertAll(events).size
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,7 @@
|
|||||||
package pl.szczodrzynski.edziennik.data.db.dao
|
package pl.szczodrzynski.edziennik.data.db.dao
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.room.Dao
|
import androidx.room.*
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.OnConflictStrategy
|
|
||||||
import androidx.room.RawQuery
|
|
||||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||||
import androidx.sqlite.db.SupportSQLiteQuery
|
import androidx.sqlite.db.SupportSQLiteQuery
|
||||||
|
|
||||||
@ -24,11 +21,75 @@ interface BaseDao<T, F> {
|
|||||||
fun getOneNow(query: SupportSQLiteQuery): F?
|
fun getOneNow(query: SupportSQLiteQuery): F?
|
||||||
fun getOneNow(query: String) = getOneNow(SimpleSQLiteQuery(query))
|
fun getOneNow(query: String) = getOneNow(SimpleSQLiteQuery(query))
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
/**
|
||||||
|
* INSERT an [item] into the database,
|
||||||
|
* ignoring any conflicts.
|
||||||
|
*/
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
fun add(item: T): Long
|
fun add(item: T): Long
|
||||||
|
/**
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
* INSERT [items] into the database,
|
||||||
|
* ignoring any conflicts.
|
||||||
|
*/
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
fun addAll(items: List<T>): LongArray
|
fun addAll(items: List<T>): LongArray
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REPLACE an [item] in the database,
|
||||||
|
* removing any conflicting rows.
|
||||||
|
*/
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun replace(item: T)
|
||||||
|
/**
|
||||||
|
* REPLACE [items] in the database,
|
||||||
|
* removing any conflicting rows.
|
||||||
|
*/
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun replaceAll(items: List<T>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selective UPDATE an [item] in the database.
|
||||||
|
* Do nothing if a matching item does not exist.
|
||||||
|
*/
|
||||||
|
fun update(item: T): Long
|
||||||
|
/**
|
||||||
|
* Selective UPDATE [items] in the database.
|
||||||
|
* Do nothing for those items which do not exist.
|
||||||
|
*/
|
||||||
|
fun updateAll(items: List<T>): LongArray
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all items from the database,
|
||||||
|
* that match the given [profileId].
|
||||||
|
*/
|
||||||
fun clear(profileId: Int)
|
fun clear(profileId: Int)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INSERT an [item] into the database,
|
||||||
|
* doing a selective [update] on conflicts.
|
||||||
|
* @return the newly inserted item's ID or -1L if the item was updated instead
|
||||||
|
*/
|
||||||
|
@Transaction
|
||||||
|
fun upsert(item: T): Long {
|
||||||
|
val id = add(item)
|
||||||
|
if (id == -1L) update(item)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* INSERT [items] into the database,
|
||||||
|
* doing a selective [update] on conflicts.
|
||||||
|
* @return a [LongArray] of IDs of newly inserted items or -1L if the item existed before
|
||||||
|
*/
|
||||||
|
@Transaction
|
||||||
|
fun upsertAll(items: List<T>): LongArray {
|
||||||
|
val insertResult = addAll(items)
|
||||||
|
val updateList = mutableListOf<T>()
|
||||||
|
|
||||||
|
insertResult.forEachIndexed { index, result ->
|
||||||
|
if (result == -1L) updateList.add(items[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateList.isNotEmpty()) updateAll(items)
|
||||||
|
return insertResult
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,10 @@ import androidx.room.RawQuery
|
|||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||||
import androidx.sqlite.db.SupportSQLiteQuery
|
import androidx.sqlite.db.SupportSQLiteQuery
|
||||||
|
import pl.szczodrzynski.edziennik.App
|
||||||
|
import pl.szczodrzynski.edziennik.annotation.SelectiveDao
|
||||||
|
import pl.szczodrzynski.edziennik.annotation.UpdateSelective
|
||||||
|
import pl.szczodrzynski.edziennik.data.db.AppDb
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
import pl.szczodrzynski.edziennik.data.db.entity.Event
|
||||||
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
import pl.szczodrzynski.edziennik.data.db.entity.Metadata
|
||||||
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
import pl.szczodrzynski.edziennik.data.db.full.EventFull
|
||||||
@ -17,6 +21,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date
|
|||||||
import pl.szczodrzynski.edziennik.utils.models.Time
|
import pl.szczodrzynski.edziennik.utils.models.Time
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
|
@SelectiveDao(db = AppDb::class)
|
||||||
abstract class EventDao : BaseDao<Event, EventFull> {
|
abstract class EventDao : BaseDao<Event, EventFull> {
|
||||||
companion object {
|
companion object {
|
||||||
private const val QUERY = """
|
private const val QUERY = """
|
||||||
@ -37,45 +42,21 @@ abstract class EventDao : BaseDao<Event, EventFull> {
|
|||||||
private const val NOT_BLACKLISTED = """events.eventBlacklisted = 0"""
|
private const val NOT_BLACKLISTED = """events.eventBlacklisted = 0"""
|
||||||
}
|
}
|
||||||
|
|
||||||
//abstract fun queryRaw(query: SupportSQLiteQuery)
|
private val selective by lazy { EventDaoSelective(App.db) }
|
||||||
//private fun queryRaw(query: String) = queryRaw(SimpleSQLiteQuery(query))
|
|
||||||
|
|
||||||
@Query("DELETE FROM events WHERE profileId = :profileId")
|
|
||||||
abstract override fun clear(profileId: Int)
|
|
||||||
|
|
||||||
/*fun update(event: Event) =
|
|
||||||
queryRaw("""UPDATE events SET
|
|
||||||
eventDate = '${event.date.stringY_m_d}',
|
|
||||||
eventTime = ${event.time?.stringValue},
|
|
||||||
eventTopic = '${event.topic}'""")*/
|
|
||||||
|
|
||||||
@Query("DELETE FROM events WHERE profileId = :profileId AND eventId = :id")
|
|
||||||
abstract fun remove(profileId: Int, id: Long)
|
|
||||||
|
|
||||||
@Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND thingId = :thingId")
|
|
||||||
abstract fun removeMetadata(profileId: Int, thingType: Int, thingId: Long)
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
open fun remove(profileId: Int, type: Long, id: Long) {
|
|
||||||
remove(profileId, id)
|
|
||||||
removeMetadata(profileId, if (type == Event.TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
open fun remove(event: Event) {
|
|
||||||
remove(event.profileId, event.type, event.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
open fun remove(profileId: Int, event: Event) {
|
|
||||||
remove(profileId, event.type, event.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
@RawQuery(observedEntities = [Event::class])
|
@RawQuery(observedEntities = [Event::class])
|
||||||
abstract override fun getRaw(query: SupportSQLiteQuery): LiveData<List<EventFull>>
|
abstract override fun getRaw(query: SupportSQLiteQuery): LiveData<List<EventFull>>
|
||||||
|
|
||||||
|
// SELECTIVE UPDATE
|
||||||
|
@UpdateSelective(primaryKeys = ["profileId", "eventId"], skippedColumns = ["homeworkBody", "attachmentIds", "attachmentNames"])
|
||||||
|
override fun update(item: Event) = selective.update(item)
|
||||||
|
override fun updateAll(items: List<Event>) = selective.updateAll(items)
|
||||||
|
|
||||||
|
// CLEAR
|
||||||
|
@Query("DELETE FROM events WHERE profileId = :profileId")
|
||||||
|
abstract override fun clear(profileId: Int)
|
||||||
|
|
||||||
|
// GET ALL - LIVE DATA
|
||||||
fun getAll(profileId: Int) =
|
fun getAll(profileId: Int) =
|
||||||
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId $ORDER_BY")
|
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId $ORDER_BY")
|
||||||
fun getAllByType(profileId: Int, type: Long, filter: String = "1") =
|
fun getAllByType(profileId: Int, type: Long, filter: String = "1") =
|
||||||
@ -87,8 +68,7 @@ abstract class EventDao : BaseDao<Event, EventFull> {
|
|||||||
fun getAllNearest(profileId: Int, today: Date, limit: Int) =
|
fun getAllNearest(profileId: Int, today: Date, limit: Int) =
|
||||||
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate >= '${today.stringY_m_d}' $ORDER_BY LIMIT $limit")
|
getRaw("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate >= '${today.stringY_m_d}' $ORDER_BY LIMIT $limit")
|
||||||
|
|
||||||
|
// GET ALL - NOW
|
||||||
|
|
||||||
fun getAllNow(profileId: Int) =
|
fun getAllNow(profileId: Int) =
|
||||||
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId $ORDER_BY")
|
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId $ORDER_BY")
|
||||||
fun getNotNotifiedNow() =
|
fun getNotNotifiedNow() =
|
||||||
@ -98,13 +78,11 @@ abstract class EventDao : BaseDao<Event, EventFull> {
|
|||||||
fun getAllByDateNow(profileId: Int, date: Date) =
|
fun getAllByDateNow(profileId: Int, date: Date) =
|
||||||
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate = '${date.stringY_m_d}' $ORDER_BY")
|
getRawNow("$QUERY WHERE $NOT_BLACKLISTED AND events.profileId = $profileId AND eventDate = '${date.stringY_m_d}' $ORDER_BY")
|
||||||
|
|
||||||
|
// GET ONE - NOW
|
||||||
|
|
||||||
fun getByIdNow(profileId: Int, id: Long) =
|
fun getByIdNow(profileId: Int, id: Long) =
|
||||||
getOneNow("$QUERY WHERE events.profileId = $profileId AND eventId = $id")
|
getOneNow("$QUERY WHERE events.profileId = $profileId AND eventId = $id")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Query("SELECT eventId FROM events WHERE profileId = :profileId AND eventBlacklisted = 1")
|
@Query("SELECT eventId FROM events WHERE profileId = :profileId AND eventBlacklisted = 1")
|
||||||
abstract fun getBlacklistedIds(profileId: Int): List<Long>
|
abstract fun getBlacklistedIds(profileId: Int): List<Long>
|
||||||
|
|
||||||
@ -147,4 +125,26 @@ abstract class EventDao : BaseDao<Event, EventFull> {
|
|||||||
@Query("UPDATE events SET eventBlacklisted = :blacklisted WHERE profileId = :profileId AND eventId = :eventId")
|
@Query("UPDATE events SET eventBlacklisted = :blacklisted WHERE profileId = :profileId AND eventId = :eventId")
|
||||||
abstract fun setBlacklisted(profileId: Int, eventId: Long, blacklisted: Boolean)
|
abstract fun setBlacklisted(profileId: Int, eventId: Long, blacklisted: Boolean)
|
||||||
|
|
||||||
|
@Query("DELETE FROM events WHERE profileId = :profileId AND eventId = :id")
|
||||||
|
abstract fun remove(profileId: Int, id: Long)
|
||||||
|
|
||||||
|
@Query("DELETE FROM metadata WHERE profileId = :profileId AND thingType = :thingType AND thingId = :thingId")
|
||||||
|
abstract fun removeMetadata(profileId: Int, thingType: Int, thingId: Long)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun remove(profileId: Int, type: Long, id: Long) {
|
||||||
|
remove(profileId, id)
|
||||||
|
removeMetadata(profileId, if (type == Event.TYPE_HOMEWORK) Metadata.TYPE_HOMEWORK else Metadata.TYPE_EVENT, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun remove(event: Event) {
|
||||||
|
remove(event.profileId, event.type, event.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open fun remove(profileId: Int, event: Event) {
|
||||||
|
remove(profileId, event.type, event.id)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ class SzkolnyAppFirebase(val app: App, val profiles: List<Profile>, val message:
|
|||||||
events += event
|
events += event
|
||||||
metadataList += metadata
|
metadataList += metadata
|
||||||
}
|
}
|
||||||
app.db.eventDao().addAll(events)
|
app.db.eventDao().upsertAll(events)
|
||||||
app.db.metadataDao().addAllReplace(metadataList)
|
app.db.metadataDao().addAllReplace(metadataList)
|
||||||
if (notificationList.isNotEmpty()) {
|
if (notificationList.isNotEmpty()) {
|
||||||
app.db.notificationDao().addAll(notificationList)
|
app.db.notificationDao().addAll(notificationList)
|
||||||
|
@ -587,7 +587,7 @@ class EventManualDialog(
|
|||||||
private fun finishAdding(eventObject: Event, metadataObject: Metadata) {
|
private fun finishAdding(eventObject: Event, metadataObject: Metadata) {
|
||||||
launch {
|
launch {
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
app.db.eventDao().add(eventObject)
|
app.db.eventDao().upsert(eventObject)
|
||||||
app.db.metadataDao().add(metadataObject)
|
app.db.metadataDao().add(metadataObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
codegen/.gitignore
vendored
Normal file
1
codegen/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
39
codegen/build.gradle
Normal file
39
codegen/build.gradle
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-28.
|
||||||
|
*/
|
||||||
|
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
|
kapt {
|
||||||
|
generateStubs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
srcDir "${buildDir.absolutePath}/tmp/kapt/main/kotlinGenerated/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
kapt project(":annotation")
|
||||||
|
compileOnly project(':annotation')
|
||||||
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
|
// configuration generator for service providers
|
||||||
|
implementation "com.google.auto.service:auto-service:1.0-rc4"
|
||||||
|
kapt "com.google.auto.service:auto-service:1.0-rc4"
|
||||||
|
kapt "androidx.room:room-compiler:${versions.room}"
|
||||||
|
implementation "androidx.room:room-runtime:${versions.room}"
|
||||||
|
implementation "com.squareup:kotlinpoet:1.5.0"
|
||||||
|
implementation "androidx.sqlite:sqlite:2.1.0@aar"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceCompatibility = "7"
|
||||||
|
targetCompatibility = "7"
|
@ -0,0 +1,330 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Kuba Szczodrzyński 2020-3-28.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pl.szczodrzynski.edziennik.codegen
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import androidx.room.TypeConverters
|
||||||
|
import com.google.auto.service.AutoService
|
||||||
|
import com.squareup.kotlinpoet.*
|
||||||
|
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||||
|
import pl.szczodrzynski.edziennik.annotation.SelectiveDao
|
||||||
|
import pl.szczodrzynski.edziennik.annotation.UpdateSelective
|
||||||
|
import java.io.File
|
||||||
|
import javax.annotation.processing.*
|
||||||
|
import javax.lang.model.SourceVersion
|
||||||
|
import javax.lang.model.element.*
|
||||||
|
import javax.lang.model.type.MirroredTypeException
|
||||||
|
import javax.lang.model.type.MirroredTypesException
|
||||||
|
import javax.lang.model.type.TypeKind
|
||||||
|
import javax.lang.model.type.TypeMirror
|
||||||
|
import javax.lang.model.util.ElementFilter
|
||||||
|
import javax.tools.Diagnostic
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
@AutoService(Processor::class)
|
||||||
|
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||||
|
@SupportedOptions(FileGenerator.KAPT_KOTLIN_GENERATED_OPTION_NAME)
|
||||||
|
class FileGenerator : AbstractProcessor() {
|
||||||
|
companion object {
|
||||||
|
const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class TypeConverter(val dataType: TypeMirror, val converterType: TypeElement, val methodName: Name, val returnType: TypeMirror)
|
||||||
|
|
||||||
|
private inline fun <reified T : Annotation> Element.getAnnotationClassValue(f: T.() -> KClass<*>) = try {
|
||||||
|
getAnnotation(T::class.java).f()
|
||||||
|
throw Exception("Expected to get a MirroredTypeException")
|
||||||
|
} catch (e: MirroredTypeException) {
|
||||||
|
e.typeMirror
|
||||||
|
}
|
||||||
|
private inline fun <reified T : Annotation> Element.getAnnotationClassValues(f: T.() -> Array<KClass<*>>) = try {
|
||||||
|
getAnnotation(T::class.java).f()
|
||||||
|
throw Exception("Expected to get a MirroredTypesException")
|
||||||
|
} catch (e: MirroredTypesException) {
|
||||||
|
e.typeMirrors
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun process(set: MutableSet<out TypeElement>?, roundEnvironment: RoundEnvironment?): Boolean {
|
||||||
|
roundEnvironment?.getElementsAnnotatedWith(SelectiveDao::class.java)?.forEach { it ->
|
||||||
|
if (it.kind != ElementKind.CLASS) {
|
||||||
|
processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Can only be applied to classes, element: $it")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val generatedSourcesRoot = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
|
||||||
|
if (generatedSourcesRoot?.isEmpty() != false) {
|
||||||
|
processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Can't find the target directory for generated Kotlin files.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = File(generatedSourcesRoot)
|
||||||
|
file.mkdirs()
|
||||||
|
|
||||||
|
val dao = it as TypeElement
|
||||||
|
processClass(dao, file)
|
||||||
|
|
||||||
|
//processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "package = $packageName, className = $className, methodName = $methodName, tableName = $tableName, paramName = $paramName, paramClass = $paramClass")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processClass(dao: TypeElement, file: File) {
|
||||||
|
val daoName = dao.simpleName.toString()
|
||||||
|
val packageName = processingEnv.elementUtils.getPackageOf(dao).toString()
|
||||||
|
|
||||||
|
val dbType = processingEnv.typeUtils.asElement(dao.getAnnotationClassValue<SelectiveDao> { db }) as TypeElement
|
||||||
|
val typeConverters = dbType.getAnnotationClassValues<TypeConverters> { value }.map {
|
||||||
|
processingEnv.typeUtils.asElement(it) as TypeElement
|
||||||
|
}.map { type ->
|
||||||
|
processingEnv.elementUtils.getAllMembers(type).mapNotNull { element ->
|
||||||
|
if (element is ExecutableElement) {
|
||||||
|
if (element.returnType.toString() == "java.lang.String"
|
||||||
|
|| element.returnType.toString() == "java.lang.Long"
|
||||||
|
|| element.returnType.toString() == "java.lang.Integer"
|
||||||
|
|| element.returnType.kind.isPrimitive) {
|
||||||
|
if (element.simpleName.startsWith("to") && element.parameters.isNotEmpty())
|
||||||
|
return@mapNotNull TypeConverter(element.parameters.first().asType(), type, element.simpleName, element.returnType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
|
||||||
|
//processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "c = ${typeConverters.joinToString()}")
|
||||||
|
|
||||||
|
val roomDatabase = ClassName("androidx.room", "RoomDatabase")
|
||||||
|
val selective = TypeSpec.classBuilder("${daoName}Selective")
|
||||||
|
.primaryConstructor(FunSpec.constructorBuilder()
|
||||||
|
.addParameter("__db", roomDatabase, KModifier.PRIVATE)
|
||||||
|
.build())
|
||||||
|
.addProperty(PropertySpec.builder("__db", roomDatabase)
|
||||||
|
.initializer("__db")
|
||||||
|
.addModifiers(KModifier.PRIVATE)
|
||||||
|
.build())
|
||||||
|
|
||||||
|
val usedTypeConverters = mutableSetOf<TypeConverter>()
|
||||||
|
|
||||||
|
processingEnv.elementUtils.getAllMembers(dao).forEach { element ->
|
||||||
|
if (element.kind != ElementKind.METHOD)
|
||||||
|
return@forEach
|
||||||
|
val method = element as ExecutableElement
|
||||||
|
val annotation = method.getAnnotation(UpdateSelective::class.java) ?: return@forEach
|
||||||
|
usedTypeConverters.addAll(processMethod(selective, method, annotation, typeConverters))
|
||||||
|
}
|
||||||
|
|
||||||
|
usedTypeConverters.forEach { converter ->
|
||||||
|
selective.addProperty(PropertySpec.builder("__${converter.converterType.simpleName}", converter.converterType.asType().asTypeName(), KModifier.PRIVATE)
|
||||||
|
.delegate(CodeBlock.builder()
|
||||||
|
.beginControlFlow("lazy")
|
||||||
|
.addStatement("%T()", converter.converterType.asType().asTypeName())
|
||||||
|
.endControlFlow()
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSpec.builder(packageName, "${daoName}Selective")
|
||||||
|
.addType(selective.build())
|
||||||
|
.build()
|
||||||
|
.writeTo(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun VariableElement.name() = getAnnotation(ColumnInfo::class.java)?.name ?: simpleName.toString()
|
||||||
|
|
||||||
|
private fun processMethod(cls: TypeSpec.Builder, method: ExecutableElement, annotation: UpdateSelective, typeConverters: List<TypeConverter>): List<TypeConverter> {
|
||||||
|
val methodName = method.simpleName.toString()
|
||||||
|
val parameter = method.parameters.first()
|
||||||
|
val paramName = parameter.simpleName.toString()
|
||||||
|
val paramTypeElement = processingEnv.typeUtils.asElement(parameter.asType()) as TypeElement
|
||||||
|
val paramTypeAnnotation = paramTypeElement.getAnnotation(Entity::class.java)
|
||||||
|
|
||||||
|
val tableName = paramTypeAnnotation.tableName
|
||||||
|
val primaryKeys = annotation.primaryKeys
|
||||||
|
val skippedColumns = annotation.skippedColumns
|
||||||
|
|
||||||
|
val members = processingEnv.elementUtils.getAllMembers(paramTypeElement)
|
||||||
|
val allFields = ElementFilter.fieldsIn(members)
|
||||||
|
allFields.removeAll { skippedColumns.contains(it.simpleName.toString()) }
|
||||||
|
allFields.removeAll { it.getAnnotation(Ignore::class.java) != null }
|
||||||
|
allFields.removeAll { field -> field.modifiers.any { it == Modifier.STATIC || it == Modifier.FINAL } }
|
||||||
|
|
||||||
|
val fields = allFields.filterNot { primaryKeys.contains(it.name()) }
|
||||||
|
val primaryFields = allFields.filter { primaryKeys.contains(it.name()) }
|
||||||
|
val fieldNames = fields.map { it.name() }
|
||||||
|
val primaryFieldNames = primaryFields.map { it.name() }
|
||||||
|
|
||||||
|
val fieldNamesQuery = fieldNames.joinToString { "$it = ?" }
|
||||||
|
val primaryFieldNamesQuery = primaryFieldNames.joinToString(" AND ") { "$it = ?" }
|
||||||
|
val query = "\"\"\"UPDATE $tableName SET $fieldNamesQuery WHERE $primaryFieldNamesQuery\"\"\""
|
||||||
|
|
||||||
|
val entityInsertionAdapter = ClassName("androidx.room", "EntityInsertionAdapter")
|
||||||
|
val supportSQLiteStatement = ClassName("androidx.sqlite.db", "SupportSQLiteStatement")
|
||||||
|
|
||||||
|
val usedTypeConverters = mutableListOf<TypeConverter>()
|
||||||
|
|
||||||
|
val bind = CodeBlock.builder()
|
||||||
|
(fields+primaryFields).forEachIndexed { i, field ->
|
||||||
|
val index = i+1
|
||||||
|
val fieldName = field.simpleName.toString()
|
||||||
|
val name = "${paramName}_$fieldName"
|
||||||
|
val realName = "${paramName}.$fieldName"
|
||||||
|
val nullable = field.getAnnotation(org.jetbrains.annotations.Nullable::class.java) != null
|
||||||
|
|
||||||
|
var param = when (field.asType().kind) {
|
||||||
|
TypeKind.BOOLEAN -> "if ($name) 1L else 0L"
|
||||||
|
TypeKind.BYTE,
|
||||||
|
TypeKind.SHORT,
|
||||||
|
TypeKind.INT -> "$name.toLong()"
|
||||||
|
TypeKind.CHAR -> "$name.toString()"
|
||||||
|
TypeKind.FLOAT -> "$name.toDouble()"
|
||||||
|
else -> when (field.asType().toString()) {
|
||||||
|
"java.lang.String" -> name
|
||||||
|
"java.lang.Boolean" -> "if ($name == true) 1L else 0L"
|
||||||
|
"java.lang.Byte",
|
||||||
|
"java.lang.Short",
|
||||||
|
"java.lang.Integer" -> "$name.toLong()"
|
||||||
|
"java.lang.Long" -> name
|
||||||
|
"java.lang.Char" -> "$name.toString()"
|
||||||
|
"java.lang.Float" -> "$name.toDouble()"
|
||||||
|
"java.lang.Double" -> name
|
||||||
|
else -> name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isConvert = false
|
||||||
|
val bindMethod = when (field.asType().kind) {
|
||||||
|
TypeKind.BOOLEAN -> "bindLong"
|
||||||
|
TypeKind.BYTE -> "bindLong"
|
||||||
|
TypeKind.SHORT -> "bindLong"
|
||||||
|
TypeKind.INT -> "bindLong"
|
||||||
|
TypeKind.LONG -> "bindLong"
|
||||||
|
TypeKind.CHAR -> "bindString"
|
||||||
|
TypeKind.FLOAT -> "bindDouble"
|
||||||
|
TypeKind.DOUBLE -> "bindDouble"
|
||||||
|
else -> when (field.asType().toString()) {
|
||||||
|
"java.lang.String" -> "bindString"
|
||||||
|
"java.lang.Boolean" -> "bindLong"
|
||||||
|
"java.lang.Byte" -> "bindLong"
|
||||||
|
"java.lang.Short" -> "bindLong"
|
||||||
|
"java.lang.Integer" -> "bindLong"
|
||||||
|
"java.lang.Long" -> "bindLong"
|
||||||
|
"java.lang.Char" -> "bindString"
|
||||||
|
"java.lang.Float" -> "bindDouble"
|
||||||
|
"java.lang.Double" -> "bindDouble"
|
||||||
|
else -> {
|
||||||
|
val converter = typeConverters.firstOrNull {
|
||||||
|
it.dataType.toString() == field.asType().toString()
|
||||||
|
}
|
||||||
|
if (converter != null) {
|
||||||
|
param = "__${converter.converterType.simpleName}.${converter.methodName}($realName)"
|
||||||
|
param = when (converter.returnType.toString()) {
|
||||||
|
"java.lang.Integer", "int",
|
||||||
|
"java.lang.Short", "short",
|
||||||
|
"java.lang.Byte", "byte" -> "$param.toLong()"
|
||||||
|
"java.lang.Boolean", "boolean" -> "if ($param) 1L else 0L"
|
||||||
|
"java.lang.Char", "char" -> "$param.toString()"
|
||||||
|
"java.lang.Float", "float" -> "$param.toDouble()"
|
||||||
|
else -> param
|
||||||
|
}
|
||||||
|
isConvert = true
|
||||||
|
usedTypeConverters += converter
|
||||||
|
when (converter.returnType.toString()) {
|
||||||
|
"java.lang.Integer", "int",
|
||||||
|
"java.lang.Short", "short",
|
||||||
|
"java.lang.Byte", "byte",
|
||||||
|
"java.lang.Boolean", "boolean" -> "bindLong"
|
||||||
|
"java.lang.Char", "char" -> "bindString"
|
||||||
|
"java.lang.Float", "float" -> "bindDouble"
|
||||||
|
else -> "bindString"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else "bind${field.asType()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isConvert) {
|
||||||
|
bind.addStatement("val $name = $realName")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bind.addStatement("val $name = $param")
|
||||||
|
param = name
|
||||||
|
}
|
||||||
|
if (nullable) {
|
||||||
|
bind.beginControlFlow("if ($name == null)")
|
||||||
|
.addStatement("stmt.bindNull($index)")
|
||||||
|
.endControlFlow()
|
||||||
|
.beginControlFlow("else")
|
||||||
|
.addStatement("stmt.$bindMethod($index, $param)")
|
||||||
|
.endControlFlow()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bind.addStatement("stmt.$bindMethod($index, $param)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val adapterName = "__insertionAdapterOf$methodName"
|
||||||
|
val delegate = CodeBlock.builder().add("""
|
||||||
|
|lazy {
|
||||||
|
| object : EntityInsertionAdapter<%T>(__db) {
|
||||||
|
| override fun createQuery() = $query
|
||||||
|
| override fun bind(stmt: %T, $paramName: %T) {
|
||||||
|
|${bind.indent().indent().indent().build()}
|
||||||
|
| }
|
||||||
|
| }
|
||||||
|
|}""".trimMargin(), paramTypeElement.asClassName(), supportSQLiteStatement, paramTypeElement.asClassName())
|
||||||
|
|
||||||
|
cls.addProperty(PropertySpec.builder(adapterName, entityInsertionAdapter.parameterizedBy(paramTypeElement.asClassName()), KModifier.PRIVATE)
|
||||||
|
.delegate(delegate.build())
|
||||||
|
.build())
|
||||||
|
|
||||||
|
val list = ClassName("kotlin.collections", "List")
|
||||||
|
val longArray = ClassName("kotlin", "LongArray")
|
||||||
|
|
||||||
|
val function = FunSpec.builder(methodName)
|
||||||
|
.addModifiers(KModifier.INTERNAL)
|
||||||
|
.addParameter("item", parameter.asType().asTypeName())
|
||||||
|
.returns(Long::class.java)
|
||||||
|
.addStatement("__db.assertNotSuspendingTransaction()")
|
||||||
|
.addStatement("__db.beginTransaction()")
|
||||||
|
.addCode("""
|
||||||
|
|try {
|
||||||
|
| val _result = $adapterName.insertAndReturnId(item)
|
||||||
|
| __db.setTransactionSuccessful()
|
||||||
|
| return _result
|
||||||
|
|} finally {
|
||||||
|
| __db.endTransaction()
|
||||||
|
|}
|
||||||
|
""".trimMargin())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val functionAll = FunSpec.builder(methodName+"All")
|
||||||
|
.addModifiers(KModifier.INTERNAL)
|
||||||
|
.addParameter("items", list.parameterizedBy(parameter.asType().asTypeName()))
|
||||||
|
.returns(longArray)
|
||||||
|
.addStatement("__db.assertNotSuspendingTransaction()")
|
||||||
|
.addStatement("__db.beginTransaction()")
|
||||||
|
.addCode("""
|
||||||
|
|try {
|
||||||
|
| val _result = $adapterName.insertAndReturnIdsArray(items)
|
||||||
|
| __db.setTransactionSuccessful()
|
||||||
|
| return _result
|
||||||
|
|} finally {
|
||||||
|
| __db.endTransaction()
|
||||||
|
|}
|
||||||
|
""".trimMargin())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
cls.addFunction(function)
|
||||||
|
cls.addFunction(functionAll)
|
||||||
|
return usedTypeConverters
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedAnnotationTypes(): MutableSet<String> {
|
||||||
|
return mutableSetOf(SelectiveDao::class.java.canonicalName)
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
include ':codegen'
|
||||||
|
include ':annotation'
|
||||||
rootProject.name='Szkolny.eu'
|
rootProject.name='Szkolny.eu'
|
||||||
include ':app', ':agendacalendarview', ':mhttp', ':material-about-library', ':cafebar', ':szkolny-font', ':nachos'
|
include ':app', ':agendacalendarview', ':mhttp', ':material-about-library', ':cafebar', ':szkolny-font', ':nachos'
|
||||||
/*
|
/*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user