1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.metalava.cli.common
18 
19 import com.android.SdkConstants
20 import com.android.tools.metalava.model.Codebase
21 import com.android.tools.metalava.model.text.SignatureFile
22 import java.io.File
23 
24 /** A previously released API. */
25 sealed interface PreviouslyReleasedApi {
26 
27     /** The set of files defining the previously released API. */
28     val files: List<File>
29 
30     /** The last signature file, if any, defining the previously released API. */
31     val lastSignatureFile: File?
32 
33     /** Load the files into a list of [Codebase]s. */
34     fun load(
35         jarLoader: (File) -> Codebase,
36         signatureFileLoader: (List<SignatureFile>) -> Codebase,
37     ): Codebase
38 
39     override fun toString(): String
40 
41     companion object {
42         /**
43          * Create an optional [PreviouslyReleasedApi] instance from the list of [files] passed to
44          * the option [optionName].
45          *
46          * If [files] is empty then this returns `null`. If [files] contains a mixture of jar and
47          * non-jar files (assumed to be signature files) then it is an error. Otherwise, this will
48          * return a [JarBasedApi] or [SignatureBasedApi] for a list of jar files and a list of
49          * signature files respectively.
50          */
51         internal fun optionalPreviouslyReleasedApi(optionName: String, files: List<File>) =
52             if (files.isEmpty()) null
53             else {
54                 // Partition the files into jar and non-jar files, the latter are assumed to be
55                 // signature files.
56                 val (jarFiles, signatureFiles) =
57                     files.partition { it.path.endsWith(SdkConstants.DOT_JAR) }
58                 when {
59                     jarFiles.isEmpty() -> SignatureBasedApi.fromFiles(signatureFiles)
60                     signatureFiles.isEmpty() ->
61                         if (jarFiles.size > 1)
62                             throw IllegalStateException(
63                                 "$optionName: Cannot have more than one jar file, found: ${jarFiles.joinToString()}"
64                             )
65                         else JarBasedApi(jarFiles[0])
66                     else ->
67                         throw IllegalStateException(
68                             "$optionName: Cannot mix jar files (e.g. ${jarFiles.first()}) and signature files (e.g. ${signatureFiles.first()})"
69                         )
70                 }
71             }
72     }
73 }
74 
75 /** A previously released API defined by jar files. */
76 data class JarBasedApi(val file: File) : PreviouslyReleasedApi {
77 
78     override val files: List<File> = listOf(file)
79 
80     /** This does not have any signature files, so it always returns `null`. */
81     override val lastSignatureFile: File? = null
82 
loadnull83     override fun load(
84         jarLoader: (File) -> Codebase,
85         signatureFileLoader: (List<SignatureFile>) -> Codebase,
86     ) = jarLoader(file)
87 
88     override fun toString(): String {
89         return file.toString()
90     }
91 }
92 
93 /**
94  * A previously released API defined by signature files.
95  *
96  * If a single file is provided then it may be a full API or a delta on another API. If multiple
97  * files are provided then they are expected to be provided in order from the narrowest API to the
98  * widest API, where all but the first files are deltas on the preceding file.
99  */
100 data class SignatureBasedApi(val signatureFiles: List<SignatureFile>) : PreviouslyReleasedApi {
101 
<lambda>null102     override val files: List<File> = signatureFiles.map { it.file }
103 
104     override val lastSignatureFile = signatureFiles.last().file
105 
loadnull106     override fun load(
107         jarLoader: (File) -> Codebase,
108         signatureFileLoader: (List<SignatureFile>) -> Codebase,
109     ) = signatureFileLoader(signatureFiles)
110 
111     override fun toString(): String {
112         return signatureFiles.joinToString(",") { it.file.path }
113     }
114 
115     companion object {
fromFilesnull116         fun fromFiles(files: List<File>): SignatureBasedApi {
117             val lastIndex = files.size - 1
118             return SignatureBasedApi(
119                 files.mapIndexed { index, file ->
120                     SignatureFile(
121                         file,
122                         // The last file is assumed to be for the current API surface.
123                         forCurrentApiSurface = index == lastIndex,
124                     )
125                 }
126             )
127         }
128     }
129 }
130