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 <int>:<int> values, where the first 37 * <int> is the integer ID of an SDK, and the second <int> 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(""") // $NON-NLS-1$ 262 } 263 '<' -> { 264 append("<") // $NON-NLS-1$ 265 } 266 '\'' -> { 267 append("'") // $NON-NLS-1$ 268 } 269 '&' -> { 270 append("&") // $NON-NLS-1$ 271 } 272 else -> { 273 append(c) 274 } 275 } 276 } 277 } 278 } 279 } 280 } 281