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 /** 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.isNotEmpty()) { 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 fun defaultValueString(): String = defaultValue()?.toString() ?: "null" 138 139 fun hasTypeArguments(): Boolean = toTypeString().contains("<") 140 141 /** 142 * If this type is a type parameter, then return the corresponding [TypeParameterItem]. 143 * The optional [context] provides the method or class where this type parameter 144 * appears, and can be used for example to resolve the bounds for a type variable 145 * used in a method that was specified on the class. 146 */ 147 fun asTypeParameter(context: MemberItem? = null): TypeParameterItem? 148 149 /** 150 * Whether this type is a type parameter. 151 */ 152 fun isTypeParameter(context: MemberItem? = null): Boolean = asTypeParameter(context) != null 153 154 /** 155 * Mark nullness annotations in the type as recent. 156 * TODO: This isn't very clean; we should model individual annotations. 157 */ 158 fun markRecent() 159 160 /** Returns true if this type represents an array of one or more dimensions */ 161 fun isArray(): Boolean = arrayDimensions() > 0 162 163 /** 164 * Ensure that we don't include any annotations in the type strings for this type. 165 */ 166 fun scrubAnnotations() 167 168 companion object { 169 /** Shortens types, if configured */ 170 fun shortenTypes(type: String): String { 171 if (compatibility.omitCommonPackages) { 172 var cleaned = type 173 if (cleaned.contains("@androidx.annotation.")) { 174 cleaned = cleaned.replace("@androidx.annotation.", "@") 175 } 176 if (cleaned.contains("@android.support.annotation.")) { 177 cleaned = cleaned.replace("@android.support.annotation.", "@") 178 } 179 180 return stripJavaLangPrefix(cleaned) 181 } 182 183 return type 184 } 185 186 /** 187 * Removes java.lang. prefixes from types, unless it's in a subpackage such 188 * as java.lang.reflect. For simplicity we may also leave inner classes 189 * in the java.lang package untouched. 190 * 191 * NOTE: We only remove this from the front of the type; e.g. we'll replace 192 * java.lang.Class<java.lang.String> with Class<java.lang.String>. 193 * This is because the signature parsing of types is not 100% accurate 194 * and we don't want to run into trouble with more complicated generic 195 * type signatures where we end up not mapping the simplified types back 196 * to the real fully qualified type names. 197 */ 198 fun stripJavaLangPrefix(type: String): String { 199 if (type.startsWith(JAVA_LANG_PREFIX)) { 200 // Replacing java.lang is harder, since we don't want to operate in sub packages, 201 // e.g. java.lang.String -> String, but java.lang.reflect.Method -> unchanged 202 val start = JAVA_LANG_PREFIX.length 203 val end = type.length 204 for (index in start until end) { 205 if (type[index] == '<') { 206 return type.substring(start) 207 } else if (type[index] == '.') { 208 return type 209 } 210 } 211 212 return type.substring(start) 213 } 214 215 return type 216 } 217 218 fun formatType(type: String?): String { 219 if (type == null) { 220 return "" 221 } 222 223 var cleaned = type 224 225 if (compatibility.spaceAfterCommaInTypes && cleaned.indexOf(',') != -1) { 226 // The compat files have spaces after commas where we normally don't 227 cleaned = cleaned.replace(",", ", ").replace(", ", ", ") 228 } 229 230 cleaned = cleanupGenerics(cleaned) 231 return cleaned 232 } 233 234 fun cleanupGenerics(signature: String): String { 235 // <T extends java.lang.Object> is the same as <T> 236 // but NOT for <T extends Object & java.lang.Comparable> -- you can't 237 // shorten this to <T & java.lang.Comparable 238 // return type.replace(" extends java.lang.Object", "") 239 return signature.replace(" extends java.lang.Object>", ">") 240 } 241 242 val comparator: Comparator<TypeItem> = Comparator { type1, type2 -> 243 val cls1 = type1.asClass() 244 val cls2 = type2.asClass() 245 if (cls1 != null && cls2 != null) { 246 ClassItem.fullNameComparator.compare(cls1, cls2) 247 } else { 248 type1.toTypeString().compareTo(type2.toTypeString()) 249 } 250 } 251 252 fun convertTypeString(typeString: String, replacementMap: Map<String, String>?): String { 253 var string = typeString 254 if (replacementMap != null && replacementMap.isNotEmpty()) { 255 // This is a moved method (typically an implementation of an interface 256 // method provided in a hidden superclass), with generics signatures. 257 // We need to rewrite the generics variables in case they differ 258 // between the classes. 259 if (replacementMap.isNotEmpty()) { 260 replacementMap.forEach { (from, to) -> 261 // We can't just replace one string at a time: 262 // what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C? 263 // If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions 264 // simultaneously I get B,C,C. Therefore, we insert "___" as a magical prefix to prevent 265 // scenarios like this, and then we'll drop them afterwards. 266 string = string.replace(Regex(pattern = """\b$from\b"""), replacement = "___$to") 267 } 268 } 269 string = string.replace("___", "") 270 return string 271 } else { 272 return string 273 } 274 } 275 276 // Copied from doclava1 277 fun toSlashFormat(typeName: String): String { 278 var name = typeName 279 var dimension = "" 280 while (name.endsWith("[]")) { 281 dimension += "[" 282 name = name.substring(0, name.length - 2) 283 } 284 285 val base: String 286 base = when (name) { 287 "void" -> "V" 288 "byte" -> "B" 289 "boolean" -> "Z" 290 "char" -> "C" 291 "short" -> "S" 292 "int" -> "I" 293 "long" -> "L" 294 "float" -> "F" 295 "double" -> "D" 296 else -> "L" + ClassContext.getInternalName(name) + ";" 297 } 298 299 return dimension + base 300 } 301 302 /** Compares two strings, ignoring space diffs (spaces, not whitespace in general) */ 303 fun equalsWithoutSpace(s1: String, s2: String): Boolean { 304 if (s1 == s2) { 305 return true 306 } 307 val sp1 = s1.indexOf(' ') // first space 308 val sp2 = s2.indexOf(' ') 309 if (sp1 == -1 && sp2 == -1) { 310 // no spaces in strings and aren't equal 311 return false 312 } 313 314 val l1 = s1.length 315 val l2 = s2.length 316 var i1 = 0 317 var i2 = 0 318 319 while (i1 < l1 && i2 < l2) { 320 var c1 = s1[i1++] 321 var c2 = s2[i2++] 322 323 while (c1 == ' ' && i1 < l1) { 324 c1 = s1[i1++] 325 } 326 while (c2 == ' ' && i2 < l2) { 327 c2 = s2[i2++] 328 } 329 if (c1 != c2) { 330 return false 331 } 332 } 333 // Skip trailing spaces 334 while (i1 < l1 && s1[i1] == ' ') { 335 i1++ 336 } 337 while (i2 < l2 && s2[i2] == ' ') { 338 i2++ 339 } 340 return i1 == l1 && i2 == l2 341 } 342 } 343 } 344