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