xtype_ext.kt

/*
 * Copyright 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.ext

import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.isArray
import androidx.room.compiler.processing.isByte
import androidx.room.compiler.processing.isEnum
import androidx.room.compiler.processing.isKotlinUnit
import androidx.room.compiler.processing.isVoid
import androidx.room.compiler.processing.isVoidObject
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.TypeName

/**
 * Returns `true` if this type is not the `void` type.
 */
fun XType.isNotVoid() = !isVoid()

/**
 * Returns `true` if this does not represent a [Void] type.
 */
fun XType.isNotVoidObject() = !isVoidObject()

/**
 * Returns `true` if this type does not represent a [Unit] type.
 */
fun XType.isNotKotlinUnit() = !isKotlinUnit()

/**
 * Returns `true` if this type represents a valid resolvable type.
 */
fun XType.isNotError() = !isError()

/**
 * Returns `true` if this is not the None type.
 */
fun XType.isNotNone() = !isNone()

/**
 * Returns `true` if this is not `byte` type.
 */
fun XType.isNotByte() = !isByte()

/**
 * Returns `true` if this represents a `UUID` type.
 */
fun XType.isUUID(): Boolean = typeName == CommonTypeNames.UUID

/**
 * Checks if the class of the provided type has the equals() and hashCode() methods declared.
 *
 * Certain Room types and database primitive types are considered to implements equals and
 * hashcode.
 *
 * If they are not found at the current class level, the method recursively moves on to the
 * super class level and continues to look for these declared methods.
 */
fun XType.implementsEqualsAndHashcode(): Boolean {
    if (this.isSupportedMapTypeArg()) return true

    val typeElement = this.typeElement ?: return false
    if (typeElement.className == ClassName.OBJECT) {
        return false
    }

    if (typeElement.isDataClass()) {
        return true
    }
    val hasEquals = typeElement.getDeclaredMethods().any {
        it.jvmName == "equals" &&
            it.returnType.typeName == TypeName.BOOLEAN &&
            it.parameters.count() == 1 &&
            it.parameters[0].type.typeName == TypeName.OBJECT
    }
    val hasHashCode = typeElement.getDeclaredMethods().any {
        it.jvmName == "hashCode" &&
            it.returnType.typeName == TypeName.INT &&
            it.parameters.count() == 0
    }

    if (hasEquals && hasHashCode) return true

    return typeElement.superClass?.implementsEqualsAndHashcode() ?: false
}

/**
 * Checks if the class of the provided type is one of the types supported in Dao functions with a
 * Map or Multimap return type.
 */
fun XType.isSupportedMapTypeArg(): Boolean {
    if (this.typeName.isPrimitive) return true
    if (this.typeName.isBoxedPrimitive) return true
    if (this.typeName == CommonTypeNames.STRING) return true
    if (this.isTypeOf(ByteArray::class)) return true
    if (this.isArray() && this.isByte()) return true
    val typeElement = this.typeElement ?: return false
    if (typeElement.isEnum()) return true
    return false
}