ThrowableParcelConverterFileGenerator.kt
/*
* Copyright 2022 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.privacysandbox.tools.core.generator
import androidx.privacysandbox.tools.core.generator.AidlGenerator.Companion.parcelableStackFrameName
import androidx.privacysandbox.tools.core.generator.AidlGenerator.Companion.throwableParcelName
import androidx.privacysandbox.tools.core.generator.SpecNames.cancellationExceptionClass
import androidx.privacysandbox.tools.core.generator.SpecNames.stackTraceElementClass
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.TypeSpec
class ThrowableParcelConverterFileGenerator(
private val basePackageName: String,
private val target: GenerationTarget,
) {
companion object {
const val converterName = "${throwableParcelName}Converter"
fun toThrowableParcelNameSpec(packageName: String) = MemberName(ClassName(
packageName,
converterName
), "toThrowableParcel")
fun fromThrowableParcelNameSpec(packageName: String) = MemberName(ClassName(
packageName,
converterName
), "fromThrowableParcel")
}
private val throwableParcelNameSpec = ClassName(basePackageName, throwableParcelName)
private val parcelableStackFrameNameSpec = ClassName(basePackageName, parcelableStackFrameName)
private val toThrowableParcelNameSpec = toThrowableParcelNameSpec(basePackageName)
private val fromThrowableParcelNameSpec = fromThrowableParcelNameSpec(basePackageName)
fun generate() =
FileSpec.builder(
basePackageName,
converterName
).build {
addCommonSettings()
addType(generateConverter())
}
private fun generateConverter() =
TypeSpec.objectBuilder(ClassName(basePackageName, converterName)).build {
when (target) {
GenerationTarget.CLIENT -> addFunction(generateFromThrowableParcel())
GenerationTarget.SERVER -> addFunction(generateToThrowableParcel())
}
}
private fun generateToThrowableParcel() =
FunSpec.builder(toThrowableParcelNameSpec.simpleName).build {
addParameter("throwable", Throwable::class)
returns(throwableParcelNameSpec)
addCode {
add(
"""
val parcel = %T()
parcel.exceptionClass = throwable::class.qualifiedName
parcel.errorMessage = throwable.message
parcel.stackTrace = throwable.stackTrace.map {
val stackFrame = %T()
stackFrame.declaringClass = it.className
stackFrame.methodName = it.methodName
stackFrame.fileName = it.fileName
stackFrame.lineNumber = it.lineNumber
stackFrame
}.toTypedArray()
throwable.cause?.let {
parcel.cause = arrayOf(${toThrowableParcelNameSpec.simpleName}(it))
}
parcel.suppressedExceptions =
throwable.suppressedExceptions.map {
${toThrowableParcelNameSpec.simpleName}(it)
}.toTypedArray()
parcel.isCancellationException = throwable is %T
return parcel
""".trimIndent(),
throwableParcelNameSpec,
parcelableStackFrameNameSpec,
cancellationExceptionClass,
)
}
}
private fun generateFromThrowableParcel() =
FunSpec.builder(fromThrowableParcelNameSpec.simpleName).build {
addParameter("throwableParcel", throwableParcelNameSpec)
returns(Throwable::class)
addCode {
add(
"""
val exceptionClass = throwableParcel.exceptionClass
val stackTrace = throwableParcel.stackTrace
val errorMessage = "[${'$'}exceptionClass] ${'$'}{throwableParcel.errorMessage}"
val cause = throwableParcel.cause?.firstOrNull()?.let {
${fromThrowableParcelNameSpec.simpleName}(it)
}
val exception = if (throwableParcel.isCancellationException) {
PrivacySandboxCancellationException(errorMessage, cause)
} else {
PrivacySandboxException(errorMessage, cause)
}
for (suppressed in throwableParcel.suppressedExceptions) {
exception.addSuppressed(${fromThrowableParcelNameSpec.simpleName}(suppressed))
}
exception.stackTrace =
stackTrace.map {
%T(
it.declaringClass,
it.methodName,
it.fileName,
it.lineNumber
)
}.toTypedArray()
return exception
""".trimIndent(),
stackTraceElementClass,
)
}
}
}