blob: 108649d5ebaa49148579023c97bc9660923639db [file] [log] [blame]
/*
* Copyright (C) 2023 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 com.android.tools.metalava
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.ClassResolver
import com.android.tools.metalava.model.Codebase
import com.android.tools.metalava.model.ConstructorItem
import com.android.tools.metalava.model.DefaultModifierList
import com.android.tools.metalava.model.FieldItem
import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.PackageItem
import com.android.tools.metalava.model.PropertyItem
import com.android.tools.metalava.model.text.ReferenceResolver
import com.android.tools.metalava.model.text.ResolverContext
import com.android.tools.metalava.model.text.SourcePositionInfo
import com.android.tools.metalava.model.text.TextClassItem
import com.android.tools.metalava.model.text.TextCodebase
import com.android.tools.metalava.model.text.TextConstructorItem
import com.android.tools.metalava.model.text.TextFieldItem
import com.android.tools.metalava.model.text.TextMethodItem
import com.android.tools.metalava.model.text.TextPackageItem
import com.android.tools.metalava.model.text.TextPropertyItem
import com.android.tools.metalava.model.visitors.ApiVisitor
import java.io.File
/** File conversion tasks */
internal data class ConvertFile(
val fromApiFile: File,
val outputFile: File,
val baseApiFile: File? = null,
val strip: Boolean = false
)
/** Perform the file conversion described by the [ConvertFile] on which this is called. */
internal fun ConvertFile.process(progressTracker: ProgressTracker) {
val annotationManager = DefaultAnnotationManager()
val signatureApi = SignatureFileLoader.load(fromApiFile, annotationManager = annotationManager)
val apiVisitorConfig = ApiVisitor.Config()
val apiPredicateConfig = apiVisitorConfig.apiPredicateConfig
val apiType = ApiType.ALL
val apiEmit = apiType.getEmitFilter(apiPredicateConfig)
val strip = strip
val apiReference =
if (strip) apiType.getEmitFilter(apiPredicateConfig)
else apiType.getReferenceFilter(apiPredicateConfig)
val baseFile = baseApiFile
val outputApi =
if (baseFile != null) {
// Convert base on a diff
val baseApi = SignatureFileLoader.load(baseFile, annotationManager = annotationManager)
computeDelta(baseFile, baseApi, signatureApi, apiVisitorConfig)
} else {
signatureApi
}
// See JDiff's XMLToAPI#nameAPI
val apiName = outputFile.nameWithoutExtension.replace(' ', '_')
createReportFile(progressTracker, outputApi, outputFile, "JDiff File") { printWriter ->
JDiffXmlWriter(
printWriter,
apiEmit,
apiReference,
signatureApi.preFiltered && !strip,
apiName,
showUnannotated = false,
ApiVisitor.Config(),
)
}
}
/**
* Create a [TextCodebase] that is a delta between [baseApi] and [signatureApi], i.e. it includes
* all the [Item] that are in [signatureApi] but not in [baseApi].
*
* This is expected to be used where [signatureApi] is a super set of [baseApi] but that is not
* enforced. If [baseApi] contains [Item]s which are not present in [signatureApi] then they will
* not appear in the delta.
*
* [ClassItem]s are treated specially. If [signatureApi] and [baseApi] have [ClassItem]s with the
* same name and [signatureApi]'s has members which are not present in [baseApi]'s then a
* [ClassItem] containing the additional [signatureApi] members will appear in the delta, otherwise
* it will not.
*
* @param baseFile the [Codebase.location] used for the resulting delta.
* @param baseApi the base [Codebase] whose [Item]s will not appear in the delta.
* @param signatureApi the extending [Codebase] whose [Item]s will appear in the delta as long as
* they are not part of [baseApi].
*/
private fun computeDelta(
baseFile: File,
baseApi: Codebase,
signatureApi: Codebase,
apiVisitorConfig: ApiVisitor.Config,
): TextCodebase {
// Compute just the delta
val delta = TextCodebase(baseFile, signatureApi.annotationManager)
delta.description = "Delta between $baseApi and $signatureApi"
CodebaseComparator(apiVisitorConfig = apiVisitorConfig)
.compare(
object : ComparisonVisitor() {
override fun added(new: PackageItem) {
delta.addPackage(new as TextPackageItem)
}
override fun added(new: ClassItem) {
val pkg = getOrAddPackage(new.containingPackage().qualifiedName())
pkg.addClass(new as TextClassItem)
}
override fun added(new: ConstructorItem) {
val cls = getOrAddClass(new.containingClass())
cls.addConstructor(new as TextConstructorItem)
}
override fun added(new: MethodItem) {
val cls = getOrAddClass(new.containingClass())
cls.addMethod(new as TextMethodItem)
}
override fun added(new: FieldItem) {
val cls = getOrAddClass(new.containingClass())
cls.addField(new as TextFieldItem)
}
override fun added(new: PropertyItem) {
val cls = getOrAddClass(new.containingClass())
cls.addProperty(new as TextPropertyItem)
}
private fun getOrAddClass(fullClass: ClassItem): TextClassItem {
val cls = delta.findClass(fullClass.qualifiedName())
if (cls != null) {
return cls
}
val textClass = fullClass as TextClassItem
val newClass =
TextClassItem(
delta,
SourcePositionInfo.UNKNOWN,
textClass.modifiers,
textClass.isInterface(),
textClass.isEnum(),
textClass.isAnnotationType(),
textClass.qualifiedName,
textClass.qualifiedName,
textClass.name,
textClass.annotations,
textClass.typeParameterList
)
val pkg = getOrAddPackage(fullClass.containingPackage().qualifiedName())
pkg.addClass(newClass)
newClass.setContainingPackage(pkg)
delta.registerClass(newClass)
return newClass
}
private fun getOrAddPackage(pkgName: String): TextPackageItem {
val pkg = delta.findPackage(pkgName)
if (pkg != null) {
return pkg
}
val newPkg =
TextPackageItem(
delta,
pkgName,
DefaultModifierList(delta, DefaultModifierList.PUBLIC),
SourcePositionInfo.UNKNOWN
)
delta.addPackage(newPkg)
return newPkg
}
},
baseApi,
signatureApi,
ApiType.ALL.getReferenceFilter(apiVisitorConfig.apiPredicateConfig)
)
// As the delta has not been created by the parser there is no parser provided
// context to use so just use an empty context.
val context =
object : ResolverContext {
override fun namesOfInterfaces(cl: TextClassItem): List<String>? = null
override fun nameOfSuperClass(cl: TextClassItem): String? = null
override val classResolver: ClassResolver? = null
}
// All this actually does is add in an appropriate super class depending on the class
// type.
ReferenceResolver.resolveReferences(context, delta)
return delta
}