blob: 2f8ae17b3cb4436cfad5a50e335afe248fa42144 [file] [log] [blame]
/*
* 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.
*/
package androidx.room.solver.types
import androidx.annotation.VisibleForTesting
import androidx.room.compiler.processing.XType
import androidx.room.ext.L
import androidx.room.ext.T
import androidx.room.solver.CodeGenScope
/**
* A code generator that can convert from 1 type to another
*/
abstract class TypeConverter(
val from: XType,
val to: XType,
val cost: Cost = Cost.CONVERTER
) {
/**
* Should generate the code that will covert [inputVarName] of type [from] to [outputVarName]
* of type [to]. This method *should not* declare the [outputVarName] as it is already
* declared by the caller.
*/
protected abstract fun doConvert(
inputVarName: String,
outputVarName: String,
scope: CodeGenScope
)
/**
* A type converter can optionally override this method if they can handle the case where
* they don't need a temporary output variable (e.g. no op conversion or null checks).
*
* @return The variable name where the result is saved.
*/
protected open fun doConvert(
inputVarName: String,
scope: CodeGenScope
): String {
val outVarName = scope.getTmpVar()
scope.builder().apply {
addStatement("final $T $L", to.typeName, outVarName)
}
doConvert(
inputVarName = inputVarName,
outputVarName = outVarName,
scope = scope
)
return outVarName
}
fun convert(
inputVarName: String,
scope: CodeGenScope
): String = doConvert(inputVarName, scope)
fun convert(
inputVarName: String,
outputVarName: String,
scope: CodeGenScope
) {
doConvert(inputVarName, outputVarName, scope)
}
/**
* Represents the cost of a type converter.
*
* When calculating cost, we consider multiple types of conversions in ascending order, from
* cheapest to expensive:
* * `upcast`: Converts from a subtype to super type (e.g. Int to Number)
* * `nullSafeWrapper`: Adds a null check before calling the delegated converter or else
* returns null.
* * `converter`: Unit converter
* * `requireNotNull`: Adds a null check before returning the delegated converter's value
* or throws if the value is null.
*
* The comparison happens in buckets such that having 10 upcasts is still cheaper than having
* 1 nullSafeWrapper.
*
* Internally, this class uses an IntArray to keep its fields to optimize for readability in
* operators.
*/
class Cost private constructor(
/**
* Values for each bucket, ordered from most expensive to least expensive.
*/
private val values: IntArray
) : Comparable<Cost> {
init {
require(values.size == Buckets.SIZE)
}
constructor(
converters: Int,
nullSafeWrapper: Int = 0,
upCasts: Int = 0,
requireNotNull: Int = 0
) : this(
// NOTE: construction order here MUST match the [Buckets]
intArrayOf(
requireNotNull,
converters,
nullSafeWrapper,
upCasts
)
)
@VisibleForTesting
val upCasts: Int
get() = values[Buckets.UP_CAST]
@VisibleForTesting
val nullSafeWrapper: Int
get() = values[Buckets.NULL_SAFE]
@VisibleForTesting
val requireNotNull: Int
get() = values[Buckets.REQUIRE_NOT_NULL]
@VisibleForTesting
val converters: Int
get() = values[Buckets.CONVERTER]
operator fun plus(other: Cost) = Cost(
values = IntArray(Buckets.SIZE) { index ->
values[index] + other.values[index]
}
)
override operator fun compareTo(other: Cost): Int {
for (index in 0 until Buckets.SIZE) {
val cmp = values[index].compareTo(other.values[index])
if (cmp != 0) {
return cmp
}
}
return 0
}
override fun toString() = buildString {
append("Cost[")
append("upcast:")
append(upCasts)
append(",nullsafe:")
append(nullSafeWrapper)
append(",converters:")
append(converters)
append(",requireNotNull:")
append(requireNotNull)
append("]")
}
override fun equals(other: Any?): Boolean {
if (other !is Cost) {
return false
}
return compareTo(other) == 0
}
override fun hashCode(): Int {
// we don't really use hash functions so this is good enough as a hash function.
return values[Buckets.CONVERTER]
}
companion object {
val UP_CAST = Cost(converters = 0, upCasts = 1)
val NULL_SAFE = Cost(converters = 0, nullSafeWrapper = 1)
val CONVERTER = Cost(converters = 1)
val REQUIRE_NOT_NULL = Cost(converters = 0, requireNotNull = 1)
}
/**
* Comparison buckets, ordered from the MOST expensive to LEAST expensive
*/
private object Buckets {
const val REQUIRE_NOT_NULL = 0
const val CONVERTER = 1
const val NULL_SAFE = 2
const val UP_CAST = 3
const val SIZE = 4
}
}
}