1 /* <lambda>null2 * 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 17 package com.android.tools.metalava.model 18 19 import com.android.tools.lint.detector.api.ClassContext 20 import com.android.tools.metalava.JAVA_LANG_OBJECT 21 import com.android.tools.metalava.JAVA_LANG_PREFIX 22 import com.android.tools.metalava.JAVA_LANG_STRING 23 import java.util.function.Predicate 24 25 /** 26 * Whether metalava supports type use annotations. 27 * Note that you can't just turn this flag back on; you have to 28 * also add TYPE_USE back to the handful of nullness 29 * annotations in stub-annotations/src/main/java/. 30 */ 31 const val SUPPORT_TYPE_USE_ANNOTATIONS = false 32 33 /** Represents a {@link https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Type.html Type} */ 34 interface TypeItem { 35 /** 36 * Generates a string for this type. 37 * 38 * For a type like this: @Nullable java.util.List<@NonNull java.lang.String>, 39 * [outerAnnotations] controls whether the top level annotation like @Nullable 40 * is included, [innerAnnotations] controls whether annotations like @NonNull 41 * are included, and [erased] controls whether we return the string for 42 * the raw type, e.g. just "java.util.List". The [kotlinStyleNulls] parameter 43 * controls whether it should return "@Nullable List<String>" as "List<String!>?". 44 * Finally, [filter] specifies a filter to apply to the type annotations, if 45 * any. 46 * 47 * (The combination [outerAnnotations] = true and [innerAnnotations] = false 48 * is not allowed.) 49 */ 50 fun toTypeString( 51 outerAnnotations: Boolean = false, 52 innerAnnotations: Boolean = outerAnnotations, 53 erased: Boolean = false, 54 kotlinStyleNulls: Boolean = false, 55 context: Item? = null, 56 filter: Predicate<Item>? = null 57 ): String 58 59 /** Alias for [toTypeString] with erased=true */ 60 fun toErasedTypeString(context: Item? = null): String 61 62 /** 63 * Returns the internal name of the type, as seen in bytecode. The optional [context] 64 * provides the method or class where this type appears, and can be used for example 65 * to resolve the bounds for a type variable used in a method that was specified on the class. 66 */ 67 fun internalName(context: Item? = null): String { 68 // Default implementation; PSI subclass is more accurate 69 return toSlashFormat(toErasedTypeString(context)) 70 } 71 72 /** Array dimensions of this type; for example, for String it's 0 and for String[][] it's 2. */ 73 fun arrayDimensions(): Int 74 75 fun asClass(): ClassItem? 76 77 fun toSimpleType(): String { 78 return stripJavaLangPrefix(toTypeString()) 79 } 80 81 /** 82 * Helper methods to compare types, especially types from signature files with types 83 * from parsing, which may have slightly different formats, e.g. varargs ("...") versus 84 * arrays ("[]"), java.lang. prefixes removed in wildcard signatures, etc. 85 */ 86 fun toCanonicalType(context: Item? = null): String { 87 var s = toTypeString(context = context) 88 while (s.contains(JAVA_LANG_PREFIX)) { 89 s = s.replace(JAVA_LANG_PREFIX, "") 90 } 91 if (s.contains("...")) { 92 s = s.replace("...", "[]") 93 } 94 95 return s 96 } 97 98 /** 99 * Returns the element type if the type is an array or contains a vararg. 100 * If the element is not an array or does not contain a vararg, 101 * returns the original type string. 102 */ 103 fun toElementType(): String { 104 return toErasedTypeString().replace("...", "").replace("[]", "") 105 } 106 107 val primitive: Boolean 108 109 fun typeArgumentClasses(): List<ClassItem> 110 111 fun convertType(from: ClassItem, to: ClassItem): TypeItem { 112 val map = from.mapTypeVariables(to) 113 if (map.isNotEmpty()) { 114 return convertType(map) 115 } 116 117 return this 118 } 119 120 fun convertType(replacementMap: Map<String, String>?, owner: Item? = null): TypeItem 121 122 fun convertTypeString(replacementMap: Map<String, String>?): String { 123 val typeString = 124 toTypeString(outerAnnotations = true, innerAnnotations = true, kotlinStyleNulls = false) 125 return convertTypeString(typeString, replacementMap) 126 } 127 128 fun isJavaLangObject(): Boolean { 129 return toTypeString() == JAVA_LANG_OBJECT 130 } 131 132 fun isString(): Boolean { 133 return toTypeString() == JAVA_LANG_STRING 134 } 135 136 fun defaultValue(): Any? { 137 return when (toTypeString()) { 138 "boolean" -> false 139 "char", "int", "float", "double" -> 0 140 "byte" -> 0.toByte() 141 "short" -> 0.toShort() 142 "long" -> 0L 143 else -> null 144 } 145 } 146 147 fun defaultValueString(): String = defaultValue()?.toString() ?: "null" 148 149 fun hasTypeArguments(): Boolean = toTypeString().contains("<") 150 151 /** 152 * If the item has type arguments, return a list of type arguments. 153 * If simplified is true, returns the simplified forms of the type arguments. 154 * e.g. when type arguments are <K, V extends some.arbitrary.Class>, [K, V] will be returned. 155 * If the item does not have any type arguments, return an empty list. 156 */ 157 fun typeArguments(simplified: Boolean = false): List<String> { 158 if (!hasTypeArguments()) { 159 return emptyList() 160 } 161 val typeString = toTypeString() 162 val bracketRemovedTypeString = toTypeString().indexOf('<') 163 .let { typeString.substring(it + 1, typeString.length - 1) } 164 val typeArguments = mutableListOf<String>() 165 var builder = StringBuilder() 166 var balance = 0 167 var idx = 0 168 while (idx < bracketRemovedTypeString.length) { 169 when (val s = bracketRemovedTypeString[idx]) { 170 ',' -> { 171 if (balance == 0) { 172 typeArguments.add(builder.toString()) 173 builder = StringBuilder() 174 } else { 175 builder.append(s) 176 } 177 } 178 '<' -> { 179 balance += 1 180 builder.append(s) 181 } 182 '>' -> { 183 balance -= 1 184 builder.append(s) 185 } 186 else -> builder.append(s) 187 } 188 idx += 1 189 } 190 typeArguments.add(builder.toString()) 191 192 if (simplified) { 193 return typeArguments.map { it.substringBefore(" extends ").trim() } 194 } 195 return typeArguments.map { it.trim() } 196 } 197 198 /** 199 * If this type is a type parameter, then return the corresponding [TypeParameterItem]. 200 * The optional [context] provides the method or class where this type parameter 201 * appears, and can be used for example to resolve the bounds for a type variable 202 * used in a method that was specified on the class. 203 */ 204 fun asTypeParameter(context: MemberItem? = null): TypeParameterItem? 205 206 /** 207 * Whether this type is a type parameter. 208 */ 209 fun isTypeParameter(context: MemberItem? = null): Boolean = asTypeParameter(context) != null 210 211 /** 212 * Mark nullness annotations in the type as recent. 213 * TODO: This isn't very clean; we should model individual annotations. 214 */ 215 fun markRecent() 216 217 /** Returns true if this type represents an array of one or more dimensions */ 218 fun isArray(): Boolean = arrayDimensions() > 0 219 220 /** 221 * Ensure that we don't include any annotations in the type strings for this type. 222 */ 223 fun scrubAnnotations() 224 225 companion object { 226 /** Shortens types, if configured */ 227 fun shortenTypes(type: String): String { 228 var cleaned = type 229 if (cleaned.contains("@androidx.annotation.")) { 230 cleaned = cleaned.replace("@androidx.annotation.", "@") 231 } 232 return stripJavaLangPrefix(cleaned) 233 } 234 235 /** 236 * Removes java.lang. prefixes from types, unless it's in a subpackage such 237 * as java.lang.reflect. For simplicity we may also leave inner classes 238 * in the java.lang package untouched. 239 * 240 * NOTE: We only remove this from the front of the type; e.g. we'll replace 241 * java.lang.Class<java.lang.String> with Class<java.lang.String>. 242 * This is because the signature parsing of types is not 100% accurate 243 * and we don't want to run into trouble with more complicated generic 244 * type signatures where we end up not mapping the simplified types back 245 * to the real fully qualified type names. 246 */ 247 fun stripJavaLangPrefix(type: String): String { 248 if (type.startsWith(JAVA_LANG_PREFIX)) { 249 // Replacing java.lang is harder, since we don't want to operate in sub packages, 250 // e.g. java.lang.String -> String, but java.lang.reflect.Method -> unchanged 251 val start = JAVA_LANG_PREFIX.length 252 val end = type.length 253 for (index in start until end) { 254 if (type[index] == '<') { 255 return type.substring(start) 256 } else if (type[index] == '.') { 257 return type 258 } 259 } 260 261 return type.substring(start) 262 } 263 264 return type 265 } 266 267 fun formatType(type: String?): String { 268 return if (type == null) { 269 "" 270 } else cleanupGenerics(type) 271 } 272 273 fun cleanupGenerics(signature: String): String { 274 // <T extends java.lang.Object> is the same as <T> 275 // but NOT for <T extends Object & java.lang.Comparable> -- you can't 276 // shorten this to <T & java.lang.Comparable 277 // return type.replace(" extends java.lang.Object", "") 278 return signature.replace(" extends java.lang.Object>", ">") 279 } 280 281 val comparator: Comparator<TypeItem> = Comparator { type1, type2 -> 282 val cls1 = type1.asClass() 283 val cls2 = type2.asClass() 284 if (cls1 != null && cls2 != null) { 285 ClassItem.fullNameComparator.compare(cls1, cls2) 286 } else { 287 type1.toTypeString().compareTo(type2.toTypeString()) 288 } 289 } 290 291 fun convertTypeString(typeString: String, replacementMap: Map<String, String>?): String { 292 var string = typeString 293 if (replacementMap != null && replacementMap.isNotEmpty()) { 294 // This is a moved method (typically an implementation of an interface 295 // method provided in a hidden superclass), with generics signatures. 296 // We need to rewrite the generics variables in case they differ 297 // between the classes. 298 if (replacementMap.isNotEmpty()) { 299 replacementMap.forEach { (from, to) -> 300 // We can't just replace one string at a time: 301 // what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C? 302 // If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions 303 // simultaneously I get B,C,C. Therefore, we insert "___" as a magical prefix to prevent 304 // scenarios like this, and then we'll drop them afterwards. 305 string = 306 string.replace(Regex(pattern = """\b$from\b"""), replacement = "___$to") 307 } 308 } 309 string = string.replace("___", "") 310 return string 311 } else { 312 return string 313 } 314 } 315 316 /** 317 * Convert a type string containing to its lambda representation or return the original. 318 * 319 * E.g.: `"kotlin.jvm.functions.Function1<Integer, String>"` to `"(Integer) -> String"`. 320 */ 321 fun toLambdaFormat(typeName: String): String { 322 // Bail if this isn't a Kotlin function type 323 if (!typeName.startsWith(KOTLIN_FUNCTION_PREFIX)) { 324 return typeName 325 } 326 327 // Find the first character after the first opening angle bracket. This will either be 328 // the first character of the paramTypes of the lambda if it has parameters. 329 val paramTypesStart = 330 typeName.indexOf('<', startIndex = KOTLIN_FUNCTION_PREFIX.length) + 1 331 332 // The last type param is always the return type. We find and set these boundaries with 333 // the push down loop below. 334 var paramTypesEnd = -1 335 var returnTypeStart = -1 336 337 // Get the exclusive end of the return type parameter by finding the last closing 338 // angle bracket. 339 val returnTypeEnd = typeName.lastIndexOf('>') 340 341 // Bail if an an unexpected format broke the indexOf's above. 342 if (paramTypesStart <= 0 || paramTypesStart >= returnTypeEnd) { 343 return typeName 344 } 345 346 // This loop looks for the last comma that is not inside the type parameters of a type 347 // parameter. It's a simple push down state machine that stores its depth as a counter 348 // instead of a stack. It runs backwards from the last character of the type parameters 349 // just before the last closing angle bracket to the beginning just before the first 350 // opening angle bracket. 351 var depth = 0 352 for (i in returnTypeEnd - 1 downTo paramTypesStart) { 353 val c = typeName[i] 354 355 // Increase or decrease stack depth on angle brackets 356 when (c) { 357 '>' -> depth++ 358 '<' -> depth-- 359 } 360 361 when { 362 depth == 0 -> when { // At the top level 363 c == ',' -> { 364 // When top level comma is found, mark it as the exclusive end of the 365 // parameter types and end the loop 366 paramTypesEnd = i 367 break 368 } 369 !c.isWhitespace() -> { 370 // Keep moving the start of the return type back until whitespace 371 returnTypeStart = i 372 } 373 } 374 depth < 0 -> return typeName // Bail, unbalanced nesting 375 } 376 } 377 378 // Bail if some sort of unbalanced nesting occurred or the indices around the comma 379 // appear grossly incorrect. 380 if (depth > 0 || returnTypeStart < 0 || returnTypeStart <= paramTypesEnd) { 381 return typeName 382 } 383 384 return buildString(typeName.length) { 385 append("(") 386 387 // Slice param types, if any, and append them between the parenthesis 388 if (paramTypesEnd > 0) { 389 append(typeName, paramTypesStart, paramTypesEnd) 390 } 391 392 append(") -> ") 393 394 // Slice out the return type param and append it after the arrow 395 append(typeName, returnTypeStart, returnTypeEnd) 396 } 397 } 398 399 /** Prefix of Kotlin JVM function types, used for lambdas. */ 400 private const val KOTLIN_FUNCTION_PREFIX = "kotlin.jvm.functions.Function" 401 402 // Copied from doclava1 403 fun toSlashFormat(typeName: String): String { 404 var name = typeName 405 var dimension = "" 406 while (name.endsWith("[]")) { 407 dimension += "[" 408 name = name.substring(0, name.length - 2) 409 } 410 411 val base: String 412 base = when (name) { 413 "void" -> "V" 414 "byte" -> "B" 415 "boolean" -> "Z" 416 "char" -> "C" 417 "short" -> "S" 418 "int" -> "I" 419 "long" -> "J" 420 "float" -> "F" 421 "double" -> "D" 422 else -> "L" + ClassContext.getInternalName(name) + ";" 423 } 424 425 return dimension + base 426 } 427 428 /** Compares two strings, ignoring space diffs (spaces, not whitespace in general) */ 429 fun equalsWithoutSpace(s1: String, s2: String): Boolean { 430 if (s1 == s2) { 431 return true 432 } 433 val sp1 = s1.indexOf(' ') // first space 434 val sp2 = s2.indexOf(' ') 435 if (sp1 == -1 && sp2 == -1) { 436 // no spaces in strings and aren't equal 437 return false 438 } 439 440 val l1 = s1.length 441 val l2 = s2.length 442 var i1 = 0 443 var i2 = 0 444 445 while (i1 < l1 && i2 < l2) { 446 var c1 = s1[i1++] 447 var c2 = s2[i2++] 448 449 while (c1 == ' ' && i1 < l1) { 450 c1 = s1[i1++] 451 } 452 while (c2 == ' ' && i2 < l2) { 453 c2 = s2[i2++] 454 } 455 if (c1 != c2) { 456 return false 457 } 458 } 459 // Skip trailing spaces 460 while (i1 < l1 && s1[i1] == ' ') { 461 i1++ 462 } 463 while (i2 < l2 && s2[i2] == ' ') { 464 i2++ 465 } 466 return i1 == l1 && i2 == l2 467 } 468 } 469 } 470