15ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar/*
25ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar * Copyright (C) 2016 The Android Open Source Project
35ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar *
45ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License");
55ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar * you may not use this file except in compliance with the License.
65ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar * You may obtain a copy of the License at
75ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar *
85ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar *      http://www.apache.org/licenses/LICENSE-2.0
95ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar *
105ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar * Unless required by applicable law or agreed to in writing, software
115ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS,
125ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar * See the License for the specific language governing permissions and
145ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar * limitations under the License.
155ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar */
165ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar
1764db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyarpackage android.arch.persistence.room
185ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar
1964db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyarimport android.arch.persistence.room.processor.Context
2064db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyarimport android.arch.persistence.room.processor.DatabaseProcessor
2164db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyarimport android.arch.persistence.room.processor.ProcessorErrors
2264db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyarimport android.arch.persistence.room.vo.DaoMethod
2364db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyarimport android.arch.persistence.room.vo.Warning
2464db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyarimport android.arch.persistence.room.writer.DaoWriter
2564db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyarimport android.arch.persistence.room.writer.DatabaseWriter
265ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyarimport com.google.auto.common.BasicAnnotationProcessor
275ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyarimport com.google.auto.common.MoreElements
285ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyarimport com.google.common.collect.SetMultimap
29a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyarimport java.io.File
305ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyarimport javax.annotation.processing.SupportedSourceVersion
315ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyarimport javax.lang.model.SourceVersion
325ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyarimport javax.lang.model.element.Element
335ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar
345ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar/**
355ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar * The annotation processor for Room.
365ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar */
375ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar@SupportedSourceVersion(SourceVersion.RELEASE_7)
385ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyarclass RoomProcessor : BasicAnnotationProcessor() {
395ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar    override fun initSteps(): MutableIterable<ProcessingStep>? {
405ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar        val context = Context(processingEnv)
41275e7088223c097c1a2df718455bede42bc9efedYigit Boyar        return arrayListOf(DatabaseProcessingStep(context))
425ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar    }
435ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar
44a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    override fun getSupportedOptions(): MutableSet<String> {
45a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar        return Context.ARG_OPTIONS.toMutableSet()
46a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar    }
47a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar
4888865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar    class DatabaseProcessingStep(context: Context) : ContextBoundProcessingStep(context) {
495ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar        override fun process(elementsByAnnotation: SetMultimap<Class<out Annotation>, Element>)
505ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar                : MutableSet<Element> {
5188865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar            // TODO multi step support
5288865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar            val databases = elementsByAnnotation[Database::class.java]
535ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar                    ?.map {
54aa82fce1d73394bdc7f4c2510cf94a3572032b24Yigit Boyar                        DatabaseProcessor(context, MoreElements.asType(it)).process()
555ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar                    }
5688865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar            val allDaoMethods = databases?.flatMap { it.daoMethods }
5788865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar            allDaoMethods?.let {
58d2649c45a34db46fb03a24dcb2a443a92298b5adSergey Vasilinets                prepareDaosForWriting(databases, it)
5988865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                it.forEach {
602236f645056249c7b7b7bd9cfb67d8e5256c446dYigit Boyar                    DaoWriter(it.dao, context.processingEnv).write(context.processingEnv)
6188865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                }
6288865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar            }
6388865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar
64a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar            databases?.forEach { db ->
65a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                DatabaseWriter(db).write(context.processingEnv)
66a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                if (db.exportSchema) {
67a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                    val schemaOutFolder = context.schemaOutFolder
68a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                    if (schemaOutFolder == null) {
69a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                        context.logger.w(Warning.MISSING_SCHEMA_LOCATION, db.element,
70a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                                ProcessorErrors.MISSING_SCHEMA_EXPORT_DIRECTORY)
71a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                    } else {
72a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                        if (!schemaOutFolder.exists()) {
73a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                            schemaOutFolder.mkdirs()
74a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                        }
75a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                        val qName = db.element.qualifiedName.toString()
76a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                        val dbSchemaFolder = File(schemaOutFolder, qName)
77a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                        if (!dbSchemaFolder.exists()) {
78a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                            dbSchemaFolder.mkdirs()
79a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                        }
80a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                        db.exportSchema(File(dbSchemaFolder, "${db.version}.json"))
81a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                    }
82a64756a18111a7312b3fa03b76d13381a8907176Yigit Boyar                }
8388865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar            }
84d2649c45a34db46fb03a24dcb2a443a92298b5adSergey Vasilinets            context.databaseVerifier?.closeConnection()
855ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar            return mutableSetOf()
865ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar        }
875ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar        override fun annotations(): MutableSet<out Class<out Annotation>> {
88275e7088223c097c1a2df718455bede42bc9efedYigit Boyar            return mutableSetOf(Database::class.java, Dao::class.java, Entity::class.java)
895ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar        }
905ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar
9188865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar        /**
9288865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar         * Traverses all dao methods and assigns them suffix if they are used in multiple databases.
9388865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar         */
9464db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyar        private fun prepareDaosForWriting(
9564db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyar                databases: List<android.arch.persistence.room.vo.Database>,
9664db0cc15b78b62a1d44a70fc8b4552e660d952cYigit Boyar                daoMethods: List<DaoMethod>) {
9788865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar            daoMethods.groupBy { it.dao.typeName }
9888865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                    // if used only in 1 database, nothing to do.
9988865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                    .filter { entry -> entry.value.size > 1 }
10088865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                    .forEach { entry ->
10188865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                        entry.value.groupBy { daoMethod ->
10288865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                            // first suffix guess: Database's simple name
10388865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                            val db = databases.first { db -> db.daoMethods.contains(daoMethod) }
10488865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                            db.typeName.simpleName()
105d2649c45a34db46fb03a24dcb2a443a92298b5adSergey Vasilinets                        }.forEach { (dbName, methods) ->
10688865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                            if (methods.size == 1) {
10788865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                                //good, db names do not clash, use db name as suffix
10888865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                                methods.first().dao.setSuffix(dbName)
10988865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                            } else {
11088865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                                // ok looks like a dao is used in 2 different databases both of
11188865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                                // which have the same name. enumerate.
11288865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                                methods.forEachIndexed { index, method ->
11388865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                                    method.dao.setSuffix("${dbName}_$index")
11488865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                                }
11588865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                            }
11688865f77c35657a2bc545a718ca16a648fc8b62eYigit Boyar                        }
1175ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar                    }
1185ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar        }
1195ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar    }
1205ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar
1215ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar    abstract class ContextBoundProcessingStep(val context: Context) : ProcessingStep
1225ce90e9725b8c9e8fdcef114694d6984a0ef3141Yigit Boyar}
123