RLog.kt

/*
 * Copyright (C) 2016 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.
 */

@file:Suppress("unused")

package androidx.room.log

import androidx.room.compiler.processing.XAnnotation
import androidx.room.compiler.processing.XAnnotationValue
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.XMessager
import androidx.room.processor.Context
import androidx.room.vo.Warning
import javax.tools.Diagnostic
import javax.tools.Diagnostic.Kind.ERROR
import javax.tools.Diagnostic.Kind.NOTE
import javax.tools.Diagnostic.Kind.WARNING

class RLog(
    val messager: XMessager,
    val suppressedWarnings: Set<Warning>,
    val defaultElement: XElement?
) {
    private fun String.safeFormat(vararg args: Any): String {
        try {
            return format(args)
        } catch (ex: Throwable) {
            // the input string might be from random source in which case we rather print the
            // msg as is instead of crashing while reporting an error.
            return this
        }
    }

    fun d(element: XElement, msg: String, vararg args: Any) {
        printToMessager(messager, NOTE, msg.safeFormat(args), element)
    }

    fun d(msg: String, vararg args: Any) {
        printToMessager(messager, NOTE, msg.safeFormat(args))
    }

    fun e(element: XElement, msg: String, vararg args: Any) {
        printToMessager(messager, ERROR, msg.safeFormat(args), element)
    }

    fun e(msg: String, vararg args: Any) {
        printToMessager(messager, ERROR, msg.safeFormat(args), defaultElement)
    }

    fun w(warning: Warning, element: XElement? = null, msg: String, vararg args: Any) {
        if (suppressedWarnings.contains(warning)) {
            return
        }
        printToMessager(messager, WARNING, msg.safeFormat(args), element ?: defaultElement)
    }

    fun w(warning: Warning, msg: String, vararg args: Any) {
        if (suppressedWarnings.contains(warning)) {
            return
        }
        printToMessager(messager, WARNING, msg.safeFormat(args), defaultElement)
    }

    private data class DiagnosticMessage(
        val msg: String,
        val element: XElement?,
        val annotation: XAnnotation?,
        val annotationValue: XAnnotationValue?
    )

    class CollectingMessager : XMessager() {
        private val messages = mutableMapOf<Diagnostic.Kind, MutableList<DiagnosticMessage>>()
        override fun onPrintMessage(
            kind: Diagnostic.Kind,
            msg: String,
            element: XElement?,
            annotation: XAnnotation?,
            annotationValue: XAnnotationValue?
        ) {
            messages.getOrPut(kind) { arrayListOf() }
                .add(DiagnosticMessage(msg, element, annotation, annotationValue))
        }

        fun hasErrors() = messages.containsKey(ERROR)

        fun hasMissingTypeErrors() = messages.getOrElse(ERROR) { emptyList() }
            .any { it.msg.startsWith(MISSING_TYPE_PREFIX) }

        fun writeTo(
            context: Context,
            filterPredicate: (Diagnostic.Kind, String) -> Boolean = { _, _ -> true }
        ) {
            messages.forEach { (kind, diagnosticMessages) ->
                diagnosticMessages
                    .filter { filterPredicate(kind, it.msg) }
                    .forEach { diagnosticMessage ->
                        printToMessager(
                            context.logger.messager,
                            kind,
                            diagnosticMessage.msg,
                            diagnosticMessage.element,
                            diagnosticMessage.annotation,
                            diagnosticMessage.annotationValue
                        )
                    }
            }
        }
    }

    companion object {

        const val MISSING_TYPE_PREFIX = "[MissingType]"

        val MissingTypeErrorFilter: (Diagnostic.Kind, String) -> Boolean = { kind, msg ->
            kind == ERROR && msg.startsWith(MISSING_TYPE_PREFIX)
        }

        private fun printToMessager(
            messager: XMessager,
            kind: Diagnostic.Kind,
            msg: String,
            element: XElement? = null,
            annotation: XAnnotation? = null,
            annotationValue: XAnnotationValue? = null
        ) {
            if (element == null) {
                check(annotation == null && annotationValue == null) {
                    "If element is null, annotation and annotationValue must also be null."
                }
                messager.printMessage(kind, msg)
            } else if (annotation == null) {
                check(annotationValue == null) {
                    "If annotation is null, annotationValue must also be null."
                }
                messager.printMessage(kind, msg, element)
            } else if (annotationValue == null) {
                messager.printMessage(kind, msg, element, annotation)
            } else {
                messager.printMessage(kind, msg, element, annotation, annotationValue)
            }
        }
    }
}