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 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 /** Returns the internal name of the type, as seen in bytecode */ 64 fun internalName(): String { 65 // Default implementation; PSI subclass is more accurate 66 return toSlashFormat(toErasedTypeString()) 67 } 68 69 /** Array dimensions of this type; for example, for String it's 0 and for String[][] it's 2. */ 70 fun arrayDimensions(): Int 71 72 fun asClass(): ClassItem? 73 74 fun toSimpleType(): String { 75 return stripJavaLangPrefix(toTypeString()) 76 } 77 78 /** 79 * Helper methods to compare types, especially types from signature files with types 80 * from parsing, which may have slightly different formats, e.g. varargs ("...") versus 81 * arrays ("[]"), java.lang. prefixes removed in wildcard signatures, etc. 82 */ 83 fun toCanonicalType(context: Item? = null): String { 84 var s = toTypeString(context = context) 85 while (s.contains(JAVA_LANG_PREFIX)) { 86 s = s.replace(JAVA_LANG_PREFIX, "") 87 } 88 if (s.contains("...")) { 89 s = s.replace("...", "[]") 90 } 91 92 return s 93 } 94 95 val primitive: Boolean 96 97 fun typeArgumentClasses(): List<ClassItem> 98 99 fun convertType(from: ClassItem, to: ClassItem): TypeItem { 100 val map = from.mapTypeVariables(to) 101 if (!map.isEmpty()) { 102 return convertType(map) 103 } 104 105 return this 106 } 107 108 fun convertType(replacementMap: Map<String, String>?, owner: Item? = null): TypeItem 109 110 fun convertTypeString(replacementMap: Map<String, String>?): String { 111 val typeString = toTypeString(outerAnnotations = true, innerAnnotations = true, kotlinStyleNulls = false) 112 return convertTypeString(typeString, replacementMap) 113 } 114 115 fun isJavaLangObject(): Boolean { 116 return toTypeString() == JAVA_LANG_OBJECT 117 } 118 119 fun isString(): Boolean { 120 return toTypeString() == JAVA_LANG_STRING 121 } 122 123 fun defaultValue(): Any? { 124 return when (toTypeString()) { 125 "boolean" -> false 126 "byte" -> 0.toByte() 127 "char" -> '\u0000' 128 "short" -> 0.toShort() 129 "int" -> 0 130 "long" -> 0L 131 "float" -> 0f 132 "double" -> 0.0 133 else -> null 134 } 135 } 136 137 /** Returns true if this type references a type not matched by the given predicate */ 138 fun referencesExcludedType(filter: Predicate<Item>): Boolean { 139 if (primitive) { 140 return false 141 } 142 143 for (item in typeArgumentClasses()) { 144 if (!filter.test(item)) { 145 return true 146 } 147 } 148 149 return false 150 } 151 152 fun defaultValueString(): String = defaultValue()?.toString() ?: "null" 153 154 fun hasTypeArguments(): Boolean = toTypeString().contains("<") 155 156 /** 157 * If this type is a type parameter, then return the corresponding [TypeParameterItem]. 158 * The optional [context] provides the method or class where this type parameter 159 * appears, and can be used for example to resolve the bounds for a type variable 160 * used in a method that was specified on the class. 161 */ 162 fun asTypeParameter(context: MemberItem? = null): TypeParameterItem? 163 164 /** 165 * Mark nullness annotations in the type as recent. 166 * TODO: This isn't very clean; we should model individual annotations. 167 */ 168 fun markRecent() 169 170 /** Returns true if this type represents an array of one or more dimensions */ 171 fun isArray(): Boolean = arrayDimensions() > 0 172 173 /** 174 * Ensure that we don't include any annotations in the type strings for this type. 175 */ 176 fun scrubAnnotations() 177 178 companion object { 179 /** Shortens types, if configured */ 180 fun shortenTypes(type: String): String { 181 if (compatibility.omitCommonPackages) { 182 var cleaned = type 183 if (cleaned.contains("@androidx.annotation.")) { 184 cleaned = cleaned.replace("@androidx.annotation.", "@") 185 } 186 if (cleaned.contains("@android.support.annotation.")) { 187 cleaned = cleaned.replace("@android.support.annotation.", "@") 188 } 189 190 return stripJavaLangPrefix(cleaned) 191 } 192 193 return type 194 } 195 196 /** 197 * Removes java.lang. prefixes from types, unless it's in a subpackage such 198 * as java.lang.reflect. For simplicity we may also leave inner classes 199 * in the java.lang package untouched. 200 * 201 * NOTE: We only remove this from the front of the type; e.g. we'll replace 202 * java.lang.Class<java.lang.String> with Class<java.lang.String>. 203 * This is because the signature parsing of types is not 100% accurate 204 * and we don't want to run into trouble with more complicated generic 205 * type signatures where we end up not mapping the simplified types back 206 * to the real fully qualified type names. 207 */ 208 fun stripJavaLangPrefix(type: String): String { 209 if (type.startsWith(JAVA_LANG_PREFIX)) { 210 // Replacing java.lang is harder, since we don't want to operate in sub packages, 211 // e.g. java.lang.String -> String, but java.lang.reflect.Method -> unchanged 212 val start = JAVA_LANG_PREFIX.length 213 val end = type.length 214 for (index in start until end) { 215 if (type[index] == '<') { 216 return type.substring(start) 217 } else if (type[index] == '.') { 218 return type 219 } 220 } 221 222 return type.substring(start) 223 } 224 225 return type 226 } 227 228 fun formatType(type: String?): String { 229 if (type == null) { 230 return "" 231 } 232 233 var cleaned = type 234 235 if (compatibility.spaceAfterCommaInTypes && cleaned.indexOf(',') != -1) { 236 // The compat files have spaces after commas where we normally don't 237 cleaned = cleaned.replace(",", ", ").replace(", ", ", ") 238 } 239 240 cleaned = cleanupGenerics(cleaned) 241 return cleaned 242 } 243 244 fun cleanupGenerics(signature: String): String { 245 // <T extends java.lang.Object> is the same as <T> 246 // but NOT for <T extends Object & java.lang.Comparable> -- you can't 247 // shorten this to <T & java.lang.Comparable 248 // return type.replace(" extends java.lang.Object", "") 249 return signature.replace(" extends java.lang.Object>", ">") 250 } 251 252 val comparator: Comparator<TypeItem> = Comparator { type1, type2 -> 253 val cls1 = type1.asClass() 254 val cls2 = type2.asClass() 255 if (cls1 != null && cls2 != null) { 256 ClassItem.fullNameComparator.compare(cls1, cls2) 257 } else { 258 type1.toTypeString().compareTo(type2.toTypeString()) 259 } 260 } 261 262 fun convertTypeString(typeString: String, replacementMap: Map<String, String>?): String { 263 var string = typeString 264 if (replacementMap != null && replacementMap.isNotEmpty()) { 265 // This is a moved method (typically an implementation of an interface 266 // method provided in a hidden superclass), with generics signatures. 267 // We need to rewrite the generics variables in case they differ 268 // between the classes. 269 if (!replacementMap.isEmpty()) { 270 replacementMap.forEach { from, to -> 271 // We can't just replace one string at a time: 272 // what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C? 273 // If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions 274 // simultaneously I get B,C,C. Therefore, we insert "___" as a magical prefix to prevent 275 // scenarios like this, and then we'll drop them afterwards. 276 string = string.replace(Regex(pattern = """\b$from\b"""), replacement = "___$to") 277 } 278 } 279 string = string.replace("___", "") 280 return string 281 } else { 282 return string 283 } 284 } 285 286 // Copied from doclava1 287 fun toSlashFormat(typeName: String): String { 288 var name = typeName 289 var dimension = "" 290 while (name.endsWith("[]")) { 291 dimension += "[" 292 name = name.substring(0, name.length - 2) 293 } 294 295 val base: String 296 base = when (name) { 297 "void" -> "V" 298 "byte" -> "B" 299 "boolean" -> "Z" 300 "char" -> "C" 301 "short" -> "S" 302 "int" -> "I" 303 "long" -> "L" 304 "float" -> "F" 305 "double" -> "D" 306 else -> "L" + ClassContext.getInternalName(name) + ";" 307 } 308 309 return dimension + base 310 } 311 312 /** Compares two strings, ignoring space diffs (spaces, not whitespace in general) */ 313 fun equalsWithoutSpace(s1: String, s2: String): Boolean { 314 if (s1 == s2) { 315 return true 316 } 317 val sp1 = s1.indexOf(' ') // first space 318 val sp2 = s2.indexOf(' ') 319 if (sp1 == -1 && sp2 == -1) { 320 // no spaces in strings and aren't equal 321 return false 322 } 323 324 val l1 = s1.length 325 val l2 = s2.length 326 var i1 = 0 327 var i2 = 0 328 329 while (i1 < l1 && i2 < l2) { 330 var c1 = s1[i1++] 331 var c2 = s2[i2++] 332 333 while (c1 == ' ' && i1 < l1) { 334 c1 = s1[i1++] 335 } 336 while (c2 == ' ' && i2 < l2) { 337 c2 = s2[i2++] 338 } 339 if (c1 != c2) { 340 return false 341 } 342 } 343 // Skip trailing spaces 344 while (i1 < l1 && s1[i1] == ' ') { 345 i1++ 346 } 347 while (i2 < l2 && s2[i2] == ' ') { 348 i2++ 349 } 350 return i1 == l1 && i2 == l2 351 } 352 } 353 } 354