JavacType.kt

/*
 * Copyright (C) 2020 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.compiler.processing.javac

import androidx.room.compiler.processing.XEquality
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XRawType
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.javac.kotlin.KmType
import androidx.room.compiler.processing.safeTypeName
import com.google.auto.common.MoreTypes
import com.squareup.javapoet.TypeName
import javax.lang.model.element.ElementKind
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
import kotlin.reflect.KClass

internal abstract class JavacType(
    protected val env: JavacProcessingEnv,
    open val typeMirror: TypeMirror
) : XType, XEquality {
    // Kotlin type information about the type if this type is driven from kotlin code.
    abstract val kotlinType: KmType?

    override val rawType: XRawType by lazy {
        JavacRawType(env, this)
    }

    override fun isError() = typeMirror.kind == TypeKind.ERROR

    override fun isInt(): Boolean {
        return typeName == TypeName.INT || typeName == BOXED_INT
    }

    override fun isLong(): Boolean {
        return typeName == TypeName.LONG || typeName == BOXED_LONG
    }

    override fun isByte(): Boolean {
        return typeName == TypeName.BYTE || typeName == BOXED_BYTE
    }

    override val typeName by lazy {
        typeMirror.safeTypeName()
    }

    override fun equals(other: Any?): Boolean {
        return XEquality.equals(this, other)
    }

    override fun hashCode(): Int {
        return XEquality.hashCode(equalityItems)
    }

    override fun defaultValue(): String {
        return when (typeMirror.kind) {
            TypeKind.BOOLEAN -> "false"
            TypeKind.BYTE, TypeKind.SHORT, TypeKind.INT, TypeKind.LONG, TypeKind.CHAR -> "0"
            TypeKind.FLOAT -> "0f"
            TypeKind.DOUBLE -> "0.0"
            else -> "null"
        }
    }

    override fun boxed(): JavacType {
        return when {
            typeMirror.kind.isPrimitive -> {
                env.wrap(
                    typeMirror = env.typeUtils.boxedClass(MoreTypes.asPrimitiveType(typeMirror))
                        .asType(),
                    kotlinType = kotlinType,
                    elementNullability = XNullability.NULLABLE
                )
            }
            typeMirror.kind == TypeKind.VOID -> {
                env.wrap(
                    typeMirror = env.elementUtils.getTypeElement("java.lang.Void").asType(),
                    kotlinType = kotlinType,
                    elementNullability = XNullability.NULLABLE
                )
            }
            else -> {
                this
            }
        }
    }

    override fun asTypeElement(): XTypeElement {
        return env.wrapTypeElement(
            MoreTypes.asTypeElement(typeMirror)
        )
    }

    override fun isNone() = typeMirror.kind == TypeKind.NONE

    override fun toString(): String {
        return typeMirror.toString()
    }

    override fun extendsBound(): XType? {
        return typeMirror.extendsBound()?.let {
            env.wrap<JavacType>(
                typeMirror = it,
                kotlinType = kotlinType?.extendsBound,
                elementNullability = nullability
            )
        }
    }

    override fun isAssignableFrom(other: XType): Boolean {
        return other is JavacType && env.typeUtils.isAssignable(
            other.typeMirror,
            typeMirror
        )
    }

    override fun isTypeOf(other: KClass<*>): Boolean {
        return MoreTypes.isTypeOf(
            other.java,
            typeMirror
        )
    }

    override fun isSameType(other: XType): Boolean {
        return other is JavacType && env.typeUtils.isSameType(typeMirror, other.typeMirror)
    }

    override fun isType(): Boolean {
        return MoreTypes.isType(typeMirror)
    }

    /**
     * Create a copy of this type with the given nullability.
     * This method is not called if the nullability of the type is already equal to the given
     * nullability.
     */
    protected abstract fun copyWithNullability(nullability: XNullability): JavacType

    final override fun makeNullable(): JavacType {
        if (nullability == XNullability.NULLABLE) {
            return this
        }
        if (typeMirror.kind.isPrimitive || typeMirror.kind == TypeKind.VOID) {
            return boxed().makeNullable()
        }
        return copyWithNullability(XNullability.NULLABLE)
    }

    final override fun makeNonNullable(): JavacType {
        if (nullability == XNullability.NONNULL) {
            return this
        }
        // unlike makeNullable, we don't try to degrade to primitives here because it is valid for
        // a boxed primitive to be marked as non-null.
        return copyWithNullability(XNullability.NONNULL)
    }

    companion object {
        private val BOXED_INT = TypeName.INT.box()
        private val BOXED_LONG = TypeName.LONG.box()
        private val BOXED_BYTE = TypeName.BYTE.box()
    }

    override fun isEnum() = typeMirror.kind == TypeKind.DECLARED &&
        MoreTypes.asElement(typeMirror).kind == ElementKind.ENUM
}