@@ -31,8 +31,19 @@ import org.gradle.api.tasks.TaskAction
3131import org.sqlite.SQLiteDataSource
3232import java.io.File
3333import java.sql.Connection
34+ import java.sql.PreparedStatement
3435import java.sql.Statement
36+ import kotlin.io.extension
37+ import kotlin.io.inputStream
38+ import kotlin.io.nameWithoutExtension
39+ import kotlin.io.writeBytes
40+ import kotlin.use
3541
42+ private const val TABLE_NAME_PLACEHOLDER = " \$ {TABLE_NAME}"
43+
44+ /* *
45+ * Generates maps database from latest Room schema and map-info JSONs.
46+ */
3647@CacheableTask
3748abstract class GenerateMapsDatabaseTask : DefaultTask () {
3849 @get:InputDirectory
@@ -47,6 +58,7 @@ abstract class GenerateMapsDatabaseTask : DefaultTask() {
4758 abstract val outputFile: RegularFileProperty
4859
4960 @TaskAction
61+ @Suppress(" UndocumentedPublicFunction" ) // Described by class'es doc
5062 fun generateMapsDatabase () {
5163 val schemaFile = requireNotNull(
5264 schemaDirectory.asFileTree
@@ -82,21 +94,7 @@ abstract class GenerateMapsDatabaseTask : DefaultTask() {
8294 execute(" PRAGMA user_version = ${schema.database.version} " )
8395
8496 for (entity in schema.database.entities) {
85- execute(entity.createSql.replace(" \$ {TABLE_NAME}" , entity.tableName))
86- for (index in entity.indices) {
87- execute(index.createSql.replace(" \$ {TABLE_NAME}" , entity.tableName))
88- }
89- if (entity is RoomSchema .Database .Entity .Fts ) {
90- for (triggerCreateSql in entity.contentSyncTriggers) {
91- // Table name seems to be hard-coded in contentSyncTriggers
92- if (triggerCreateSql.contains(" \$ {TABLE_NAME}" )) {
93- throw UnsupportedOperationException (
94- " Table name parameter in contentSyncTriggers: $triggerCreateSql "
95- )
96- }
97- execute(triggerCreateSql)
98- }
99- }
97+ createEntity(entity)
10098 }
10199
102100 // To be implemented when a need arises
@@ -105,76 +103,120 @@ abstract class GenerateMapsDatabaseTask : DefaultTask() {
105103 }
106104 }
107105
106+ private fun Statement.createEntity (entity : RoomSchema .Database .Entity ) {
107+ execute(entity.createSql.replace(TABLE_NAME_PLACEHOLDER , entity.tableName))
108+
109+ for (index in entity.indices) {
110+ execute(index.createSql.replace(TABLE_NAME_PLACEHOLDER , entity.tableName))
111+ }
112+
113+ if (entity is RoomSchema .Database .Entity .Fts ) {
114+ for (triggerCreateSql in entity.contentSyncTriggers) {
115+ // Table name seems to be hard-coded in contentSyncTriggers
116+ if (triggerCreateSql.contains(TABLE_NAME_PLACEHOLDER )) {
117+ throw UnsupportedOperationException (
118+ " Table name parameter in contentSyncTriggers: $triggerCreateSql "
119+ )
120+ }
121+ execute(triggerCreateSql)
122+ }
123+ }
124+ }
125+
108126 @OptIn(ExperimentalSerializationApi ::class )
109127 private fun Connection.fillMapInfo (mapInfoFile : File ) {
110128 logger.info(" Filling data from $mapInfoFile " )
111129 val mapInfo = mapInfoFile.inputStream().use { Json .decodeFromStream<MapInfo >(it) }
112130
113- prepareStatement (
131+ withPreparedStatement (
114132 " INSERT INTO map_info (id, internal_name, floor_width, floor_height, tile_size, levels_num, floors_num) " +
115133 " VALUES (?, ?, ?, ?, ?, ?, ?)"
116- ).use { insertMapInfo ->
117- with (insertMapInfo) {
118- setInt(1 , mapInfo.id)
119- setString(2 , mapInfo.internalName)
120- setInt(3 , mapInfo.floorWidth)
121- setInt(4 , mapInfo.floorHeight)
122- setInt(5 , mapInfo.tileSize)
123- setInt(6 , mapInfo.zoomLevelsNum)
124- setInt(7 , mapInfo.floors.size)
125- executeUpdate()
126- }
134+ ) {
135+ setMapInfoParameters(mapInfo)
136+ executeUpdate()
127137 }
128138
129- prepareStatement (
139+ withPreparedStatement (
130140 " INSERT INTO map_title (map_id, language_id, title) VALUES (?, ?, ?)"
131- ).use { insertMapTitle ->
132- with (insertMapTitle) {
133- for ((language, title) in mapInfo.title.toMap()) {
134- setInt(1 , mapInfo.id)
135- setString(2 , language)
136- setString(3 , title)
137- addBatch()
138- }
139- executeBatch()
141+ ) {
142+ for ((language, title) in mapInfo.title.toMap()) {
143+ setMapTitleParameters(mapInfo.id, language, title)
144+ addBatch()
140145 }
146+ executeBatch()
141147 }
142148
143- prepareStatement(
144- " INSERT INTO marker (map_id, type, floor, x, y) VALUES (?, ?, ?, ?, ?) RETURNING id"
145- ).use { insertMarker ->
146- prepareStatement(
147- " INSERT INTO marker_text (marker_id, language_id, title, location, description)" +
148- " VALUES (?, ?, ?, ?, ?)"
149- ).use { insertMarkerText ->
150- for (floor in mapInfo.floors) {
151- for (marker in floor.markers) {
152- val markerId = with (insertMarker) {
153- setInt(1 , mapInfo.id)
154- setString(2 , marker.type)
155- setInt(3 , floor.floor)
156- setDouble(4 , marker.x.toDouble() / mapInfo.floorWidth)
157- setDouble(5 , marker.y.toDouble() / mapInfo.floorHeight)
158- executeQuery().use {
159- check(it.next())
160- it.getLong(" id" )
161- }
162- }
163-
164- for ((language, textInfo) in marker.textInfos()) {
165- with (insertMarkerText) {
166- setLong(1 , markerId)
167- setString(2 , language)
168- setString(3 , textInfo.title)
169- setString(4 , textInfo.location)
170- setString(5 , textInfo.description)
171- addBatch()
172- }
173- }
149+ usePreparedStatements(
150+ " INSERT INTO marker (map_id, type, floor, x, y) VALUES (?, ?, ?, ?, ?) RETURNING id" ,
151+ " INSERT INTO marker_text (marker_id, language_id, title, location, description)" +
152+ " VALUES (?, ?, ?, ?, ?)"
153+ ) { insertMarker, insertMarkerText ->
154+ for (floor in mapInfo.floors) {
155+ for (marker in floor.markers) {
156+ insertMarker.setMarkerParameters(
157+ mapInfo.id, floor.floor, mapInfo.floorWidth, mapInfo.floorHeight, marker
158+ )
159+ val markerId = insertMarker.executeQuery().use {
160+ check(it.next())
161+ it.getLong(" id" )
162+ }
163+ for ((language, textInfo) in marker.textInfos()) {
164+ insertMarkerText.setMarkerTestParameters(markerId, language, textInfo)
165+ insertMarkerText.addBatch()
174166 }
175167 }
176- insertMarkerText.executeBatch()
177168 }
169+ insertMarkerText.executeBatch()
178170 }
179171 }
172+
173+ private inline fun Connection.withPreparedStatement (
174+ sql : String , action : PreparedStatement .() -> Unit
175+ ) {
176+ prepareStatement(sql).use { it.action() }
177+ }
178+
179+ private inline fun Connection.usePreparedStatements (
180+ sql1 : String , sql2 : String , block : (PreparedStatement , PreparedStatement ) -> Unit
181+ ) {
182+ prepareStatement(sql1).use { s1 -> prepareStatement(sql2).use { s2 -> block(s1, s2) } }
183+ }
184+
185+ private fun PreparedStatement.setMapInfoParameters (mapInfo : MapInfo ) {
186+ setInt(1 , mapInfo.id)
187+ setString(2 , mapInfo.internalName)
188+ setInt(3 , mapInfo.floorWidth)
189+ setInt(4 , mapInfo.floorHeight)
190+ setInt(5 , mapInfo.tileSize)
191+ setInt(6 , mapInfo.zoomLevelsNum)
192+ setInt(7 , mapInfo.floors.size)
193+ }
194+
195+ private fun PreparedStatement.setMapTitleParameters (
196+ mapId : Int , language : String , title : String
197+ ) {
198+ setInt(1 , mapId)
199+ setString(2 , language)
200+ setString(3 , title)
201+ }
202+
203+ private fun PreparedStatement.setMarkerParameters (
204+ mapId : Int , floor : Int , floorWidth : Int , floorHeight : Int , marker : MapInfo .Floor .Marker
205+ ) {
206+ setInt(1 , mapId)
207+ setString(2 , marker.type)
208+ setInt(3 , floor)
209+ setDouble(4 , marker.x.toDouble() / floorWidth)
210+ setDouble(5 , marker.y.toDouble() / floorHeight)
211+ }
212+
213+ private fun PreparedStatement.setMarkerTestParameters (
214+ markerId : Long , language : String , textInfo : MapInfo .Floor .Marker .TextInfo
215+ ) {
216+ setLong(1 , markerId)
217+ setString(2 , language)
218+ setString(3 , textInfo.title)
219+ setString(4 , textInfo.location)
220+ setString(5 , textInfo.description)
221+ }
180222}
0 commit comments