Generate actual modifier in Room database constructor when the initialize function is overridden.

Bug: 359631627
Test: DatabaseObjectConstructorWriterKotlinCodeGenTest
Change-Id: If4cd1227d018d8bf7dd65ddbc5056e9d59c2c772
diff --git a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt
index 1e9bc88..4e33f4f 100644
--- a/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt
+++ b/room/integration-tests/multiplatformtestapp/src/commonTest/kotlin/androidx/room/integration/multiplatformtestapp/test/BaseAutoMigrationTest.kt
@@ -166,4 +166,6 @@
 }
 
 expect object BaseAutoMigrationTest_AutoMigrationDatabaseConstructor :
-    RoomDatabaseConstructor<AutoMigrationDatabase>
+    RoomDatabaseConstructor<AutoMigrationDatabase> {
+    override fun initialize(): AutoMigrationDatabase
+}
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt b/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
index a07fca5..0f00fc6 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
@@ -146,10 +146,10 @@
                     )
                     .write(context.processingEnv)
             }
-            if (db.constructorObjectElement != null) {
+            if (db.constructorObject != null) {
                 DatabaseObjectConstructorWriter(
                         database = db,
-                        constructorObjectElement = db.constructorObjectElement
+                        constructorObject = db.constructorObject
                     )
                     .write(context.processingEnv)
             }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index b49a6c2..6cf7930 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -34,6 +34,7 @@
 import androidx.room.vo.Dao
 import androidx.room.vo.DaoMethod
 import androidx.room.vo.Database
+import androidx.room.vo.DatabaseConstructor
 import androidx.room.vo.DatabaseView
 import androidx.room.vo.Entity
 import androidx.room.vo.FtsEntity
@@ -135,7 +136,7 @@
             errorMsg = ProcessorErrors.INVALID_DATABASE_VERSION
         )
 
-        val constructorObjectElement = processConstructorObject(element)
+        val constructorObject = processConstructorObject(element)
 
         val database =
             Database(
@@ -148,7 +149,7 @@
                 exportSchema = dbAnnotation.value.exportSchema,
                 enableForeignKeys = hasForeignKeys,
                 overrideClearAllTables = hasClearAllTables,
-                constructorObjectElement = constructorObjectElement
+                constructorObject = constructorObject
             )
         database.autoMigrations = processAutoMigrations(element, database.bundle)
         return database
@@ -543,7 +544,7 @@
         return result
     }
 
-    private fun processConstructorObject(element: XTypeElement): XTypeElement? {
+    private fun processConstructorObject(element: XTypeElement): DatabaseConstructor? {
         val annotation = element.getAnnotation(androidx.room.ConstructedBy::class)
         if (annotation == null) {
             // If no @ConstructedBy is present then validate target is JVM (including Android)
@@ -604,6 +605,16 @@
             return null
         }
 
-        return typeElement
+        val initializeExecutableElement =
+            context.processingEnv
+                .requireTypeElement(RoomTypeNames.ROOM_DB_CONSTRUCTOR)
+                .getDeclaredMethods()
+                .single()
+        val isInitOverridden =
+            typeElement.getDeclaredMethods().any {
+                it.overrides(initializeExecutableElement, typeElement)
+            }
+
+        return DatabaseConstructor(typeElement, isInitOverridden)
     }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
index aaab5ca..15f95d4 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
@@ -40,7 +40,7 @@
     val exportSchema: Boolean,
     val enableForeignKeys: Boolean,
     val overrideClearAllTables: Boolean,
-    val constructorObjectElement: XTypeElement?
+    val constructorObject: DatabaseConstructor?
 ) {
     // This variable will be set once auto-migrations are processed given the DatabaseBundle from
     // this object. This is necessary for tracking the versions involved in the auto-migration.
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/DatabaseConstructor.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/DatabaseConstructor.kt
new file mode 100644
index 0000000..f3ae2ae
--- /dev/null
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/DatabaseConstructor.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package androidx.room.vo
+
+import androidx.room.compiler.processing.XTypeElement
+
+/** Represents the declared [androidx.room.ConstructedBy] referenced object. */
+data class DatabaseConstructor(val element: XTypeElement, val overridesInitialize: Boolean)
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseObjectConstructorWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseObjectConstructorWriter.kt
index 09c9c47..7289096 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseObjectConstructorWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/DatabaseObjectConstructorWriter.kt
@@ -18,10 +18,10 @@
 
 import androidx.room.compiler.codegen.toKotlinPoet
 import androidx.room.compiler.processing.XProcessingEnv
-import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.addOriginatingElement
 import androidx.room.ext.RoomTypeNames.ROOM_DB_CONSTRUCTOR
 import androidx.room.vo.Database
+import androidx.room.vo.DatabaseConstructor
 import com.squareup.kotlinpoet.FileSpec
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
@@ -30,19 +30,19 @@
 
 class DatabaseObjectConstructorWriter(
     private val database: Database,
-    private val constructorObjectElement: XTypeElement
+    private val constructorObject: DatabaseConstructor
 ) {
     fun write(processingEnv: XProcessingEnv) {
         val databaseClassName = database.typeName.toKotlinPoet()
-        val objectClassName = constructorObjectElement.asClassName().toKotlinPoet()
+        val objectClassName = constructorObject.element.asClassName().toKotlinPoet()
         val typeSpec =
             TypeSpec.objectBuilder(objectClassName)
                 .apply {
                     addOriginatingElement(database.element)
                     addModifiers(KModifier.ACTUAL)
-                    if (constructorObjectElement.isInternal()) {
+                    if (constructorObject.element.isInternal()) {
                         addModifiers(KModifier.INTERNAL)
-                    } else if (constructorObjectElement.isPublic()) {
+                    } else if (constructorObject.element.isPublic()) {
                         addModifiers(KModifier.PUBLIC)
                     }
                     addSuperinterface(
@@ -50,9 +50,14 @@
                     )
                     addFunction(
                         FunSpec.builder("initialize")
-                            .addModifiers(KModifier.OVERRIDE)
-                            .returns(databaseClassName)
-                            .addStatement("return %L()", database.implTypeName.toKotlinPoet())
+                            .apply {
+                                if (constructorObject.overridesInitialize) {
+                                    addModifiers(KModifier.ACTUAL)
+                                }
+                                addModifiers(KModifier.OVERRIDE)
+                                returns(databaseClassName)
+                                addStatement("return %L()", database.implTypeName.toKotlinPoet())
+                            }
                             .build()
                     )
                 }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
index 0cc8204..421fb25 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/verifier/DatabaseVerifierTest.kt
@@ -404,7 +404,7 @@
             exportSchema = false,
             enableForeignKeys = false,
             overrideClearAllTables = true,
-            constructorObjectElement = null,
+            constructorObject = null,
         )
     }
 
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt
index 46f81e2..536bc08 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/vo/DatabaseTest.kt
@@ -72,7 +72,7 @@
                 exportSchema = false,
                 enableForeignKeys = false,
                 overrideClearAllTables = true,
-                constructorObjectElement = null,
+                constructorObject = null,
             )
 
         val expectedLegacyHash =
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
index bd893d3..3a6c7f7 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/writer/DatabaseObjectConstructorWriterKotlinCodeGenTest.kt
@@ -93,6 +93,26 @@
         )
     }
 
+    @Test
+    fun actualDatabaseConstructor_overridesInitialize() {
+        val ctorSrc =
+            Source.kotlin(
+                "MyDatabaseCtor.kt",
+                """
+            import androidx.room.*
+
+            expect object MyDatabaseCtor : RoomDatabaseConstructor<MyDatabase> {
+                override fun initialize(): MyDatabase
+            }
+            """
+                    .trimIndent()
+            )
+        runTest(
+            sources = listOf(databaseSrc, ctorSrc),
+            expectedFilePath = getTestGoldenPath(testName.methodName)
+        )
+    }
+
     private fun getTestGoldenPath(testName: String): String {
         return "kotlinCodeGen/$testName.kt"
     }
diff --git a/room/room-compiler/src/test/test-data/kotlinCodeGen/actualDatabaseConstructor_overridesInitialize.kt b/room/room-compiler/src/test/test-data/kotlinCodeGen/actualDatabaseConstructor_overridesInitialize.kt
new file mode 100644
index 0000000..599f927d
--- /dev/null
+++ b/room/room-compiler/src/test/test-data/kotlinCodeGen/actualDatabaseConstructor_overridesInitialize.kt
@@ -0,0 +1,5 @@
+import androidx.room.RoomDatabaseConstructor
+
+public actual object MyDatabaseCtor : RoomDatabaseConstructor<MyDatabase> {
+    actual override fun initialize(): MyDatabase = MyDatabase_Impl()
+}
\ No newline at end of file