1 /*
2  * Copyright (C) 2017 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 package com.android.tools.metalava.apilevels
17 
18 import java.io.PrintStream
19 
20 /** Represents an API element, e.g. class, method or field. */
21 open class ApiElement : Comparable<ApiElement> {
22     /** Returns the name of the API element. */
23     val name: String
24     /** The Android API level of this ApiElement. */
25     /** The Android platform SDK version this API was first introduced in. */
26     var since = 0
27         private set
28     /** The extension version of this ApiElement. */
29     /** The Android extension SDK version this API was first introduced in. */
30     var sinceExtension = NEVER
31         private set
32 
33     /**
34      * The SDKs and their versions this API was first introduced in.
35      *
36      * The value is a comma-separated list of &lt;int&gt;:&lt;int&gt; values, where the first
37      * &lt;int&gt; is the integer ID of an SDK, and the second &lt;int&gt; the version of that SDK,
38      * in which this API first appeared.
39      *
40      * This field is a super-set of mSince, and if non-null/non-empty, should be preferred.
41      */
42     private var mSdks: String? = null
43     var mainlineModule: String? = null
44         private set
45 
46     /**
47      * The API level this element was deprecated in, should only be used if [isDeprecated] is true.
48      */
49     var deprecatedIn = 0
50         private set
51 
52     private var mLastPresentIn = 0
53 
54     /**
55      * @param name the name of the API element
56      * @param version an API version for which the API element existed, or -1 if the class does not
57      *   yet exist in the Android SDK (only in extension SDKs)
58      * @param deprecated whether the API element was deprecated in the API version in question
59      */
60     internal constructor(name: String, version: Int, deprecated: Boolean = false) {
61         this.name = name
62         since = version
63         mLastPresentIn = version
64         if (deprecated) {
65             deprecatedIn = version
66         }
67     }
68 
69     /** @param name the name of the API element */
70     internal constructor(name: String) {
71         this.name = name
72     }
73 
74     /**
75      * Checks if this API element was introduced not later than another API element.
76      *
77      * @param other the API element to compare to
78      * @return true if this API element was introduced not later than `other`
79      */
introducedNotLaterThannull80     fun introducedNotLaterThan(other: ApiElement?): Boolean {
81         return since <= other!!.since
82     }
83 
84     /**
85      * Updates the API element with information for a specific API version.
86      *
87      * @param version an API version for which the API element existed
88      * @param deprecated whether the API element was deprecated in the API version in question
89      */
updatenull90     fun update(version: Int, deprecated: Boolean) {
91         assert(version > 0)
92         if (since > version) {
93             since = version
94         }
95         if (mLastPresentIn < version) {
96             mLastPresentIn = version
97         }
98         if (deprecated) {
99             // If it was not previously deprecated or was deprecated in a later version than this
100             // one then deprecate it in this version.
101             if (deprecatedIn == 0 || deprecatedIn > version) {
102                 deprecatedIn = version
103             }
104         } else {
105             // If it was previously deprecated and was deprecated in an earlier version than this
106             // one then treat it as being undeprecated.
107             if (deprecatedIn != 0 && deprecatedIn < version) {
108                 deprecatedIn = 0
109             }
110         }
111     }
112 
113     /**
114      * Updates the API element with information for a specific API version.
115      *
116      * @param version an API version for which the API element existed
117      */
updatenull118     fun update(version: Int) {
119         update(version, isDeprecated)
120     }
121 
122     /**
123      * Analogous to update(), but for extensions sdk versions.
124      *
125      * @param version an extension SDK version for which the API element existed
126      */
updateExtensionnull127     fun updateExtension(version: Int) {
128         assert(version > 0)
129         if (sinceExtension > version) {
130             sinceExtension = version
131         }
132     }
133 
updateSdksnull134     fun updateSdks(sdks: String?) {
135         mSdks = sdks
136     }
137 
updateMainlineModulenull138     fun updateMainlineModule(module: String?) {
139         mainlineModule = module
140     }
141 
142     val isDeprecated: Boolean
143         /** Checks whether the API element is deprecated or not. */
144         get() = deprecatedIn != 0
145 
146     /**
147      * Prints an XML representation of the element to a stream terminated by a line break.
148      * Attributes with values matching the parent API element are omitted.
149      *
150      * @param tag the tag of the XML element
151      * @param parentElement the parent API element
152      * @param indent the whitespace prefix to insert before the XML element
153      * @param stream the stream to print the XML element to
154      */
printnull155     open fun print(tag: String?, parentElement: ApiElement, indent: String, stream: PrintStream) {
156         print(tag, true, parentElement, indent, stream)
157     }
158 
159     /**
160      * Prints an XML representation of the element to a stream terminated by a line break.
161      * Attributes with values matching the parent API element are omitted.
162      *
163      * @param tag the tag of the XML element
164      * @param closeTag if true the XML element is terminated by "/>", otherwise the closing tag of
165      *   the element is not printed
166      * @param parentElement the parent API element
167      * @param indent the whitespace prefix to insert before the XML element
168      * @param stream the stream to print the XML element to
169      * @see .printClosingTag
170      */
printnull171     fun print(
172         tag: String?,
173         closeTag: Boolean,
174         parentElement: ApiElement,
175         indent: String?,
176         stream: PrintStream
177     ) {
178         stream.print(indent)
179         stream.print('<')
180         stream.print(tag)
181         stream.print(" name=\"")
182         stream.print(encodeAttribute(name))
183         if (!isEmpty(mainlineModule) && !isEmpty(mSdks)) {
184             stream.print("\" module=\"")
185             stream.print(encodeAttribute(mainlineModule!!))
186         }
187         if (since > parentElement.since) {
188             stream.print("\" since=\"")
189             stream.print(since)
190         }
191         if (!isEmpty(mSdks) && mSdks != parentElement.mSdks) {
192             stream.print("\" sdks=\"")
193             stream.print(mSdks)
194         }
195         if (deprecatedIn != 0 && deprecatedIn != parentElement.deprecatedIn) {
196             stream.print("\" deprecated=\"")
197             stream.print(deprecatedIn)
198         }
199         if (mLastPresentIn < parentElement.mLastPresentIn) {
200             stream.print("\" removed=\"")
201             stream.print(mLastPresentIn + 1)
202         }
203         stream.print('"')
204         if (closeTag) {
205             stream.print('/')
206         }
207         stream.println('>')
208     }
209 
isEmptynull210     private fun isEmpty(s: String?): Boolean {
211         return s.isNullOrEmpty()
212     }
213 
214     /**
215      * Prints homogeneous XML elements to a stream. Each element is printed on a separate line.
216      * Attributes with values matching the parent API element are omitted.
217      *
218      * @param elements the elements to print
219      * @param tag the tag of the XML elements
220      * @param indent the whitespace prefix to insert before each XML element
221      * @param stream the stream to print the XML elements to
222      */
printnull223     fun print(elements: Collection<ApiElement>, tag: String?, indent: String, stream: PrintStream) {
224         for (element in elements.sorted()) {
225             element.print(tag, this, indent, stream)
226         }
227     }
228 
compareTonull229     override fun compareTo(other: ApiElement): Int {
230         return name.compareTo(other.name)
231     }
232 
233     companion object {
234         const val NEVER = Int.MAX_VALUE
235 
236         /**
237          * Prints a closing tag of an XML element terminated by a line break.
238          *
239          * @param tag the tag of the element
240          * @param indent the whitespace prefix to insert before the closing tag
241          * @param stream the stream to print the XML element to
242          */
printClosingTagnull243         fun printClosingTag(tag: String?, indent: String?, stream: PrintStream) {
244             stream.print(indent)
245             stream.print("</")
246             stream.print(tag)
247             stream.println('>')
248         }
249 
encodeAttributenull250         private fun encodeAttribute(attribute: String): String {
251             return buildString {
252                 val n = attribute.length
253                 // &, ", ' and < are illegal in attributes; see
254                 // http://www.w3.org/TR/REC-xml/#NT-AttValue
255                 // (' legal in a " string and " is legal in a ' string but here we'll stay on the
256                 // safe
257                 // side).
258                 for (i in 0 until n) {
259                     when (val c = attribute[i]) {
260                         '"' -> {
261                             append("&quot;") // $NON-NLS-1$
262                         }
263                         '<' -> {
264                             append("&lt;") // $NON-NLS-1$
265                         }
266                         '\'' -> {
267                             append("&apos;") // $NON-NLS-1$
268                         }
269                         '&' -> {
270                             append("&amp;") // $NON-NLS-1$
271                         }
272                         else -> {
273                             append(c)
274                         }
275                     }
276                 }
277             }
278         }
279     }
280 }
281