| /* |
| * 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 |
| } |
| } |
| } |