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