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.compatibility 23 import com.android.tools.metalava.options 24 import java.util.function.Predicate 25 26 /** Represents a type */ 27 interface TypeItem { 28 /** 29 * Generates a string for this type. 30 * 31 * For a type like this: @Nullable java.util.List<@NonNull java.lang.String>, 32 * [outerAnnotations] controls whether the top level annotation like @Nullable 33 * is included, [innerAnnotations] controls whether annotations like @NonNull 34 * are included, and [erased] controls whether we return the string for 35 * the raw type, e.g. just "java.util.List" 36 * 37 * (The combination [outerAnnotations] = true and [innerAnnotations] = false 38 * is not allowed.) 39 */ 40 fun toTypeString( 41 outerAnnotations: Boolean = false, 42 innerAnnotations: Boolean = outerAnnotations, 43 erased: Boolean = false 44 ): String 45 46 /** Alias for [toTypeString] with erased=true */ 47 fun toErasedTypeString(): String 48 49 /** Returns the internal name of the type, as seen in bytecode */ 50 fun internalName(): String { 51 // Default implementation; PSI subclass is more accurate 52 return toSlashFormat(toErasedTypeString()) 53 } 54 55 /** Array dimensions of this type; for example, for String it's 0 and for String[][] it's 2. */ 56 fun arrayDimensions(): Int 57 58 fun asClass(): ClassItem? 59 60 fun toSimpleType(): String { 61 return stripJavaLangPrefix(toTypeString()) 62 } 63 64 /** 65 * Helper methods to compare types, especially types from signature files with types 66 * from parsing, which may have slightly different formats, e.g. varargs ("...") versus 67 * arrays ("[]"), java.lang. prefixes removed in wildcard signatures, etc. 68 */ 69 fun toCanonicalType(): String { 70 var s = toTypeString() 71 while (s.contains(JAVA_LANG_PREFIX)) { 72 s = s.replace(JAVA_LANG_PREFIX, "") 73 } 74 if (s.contains("...")) { 75 s = s.replace("...", "[]") 76 } 77 78 return s 79 } 80 81 val primitive: Boolean 82 83 fun typeArgumentClasses(): List<ClassItem> 84 85 fun convertType(from: ClassItem, to: ClassItem): TypeItem { 86 val map = from.mapTypeVariables(to) 87 if (!map.isEmpty()) { 88 return convertType(map) 89 } 90 91 return this 92 } 93 94 fun convertType(replacementMap: Map<String, String>?, owner: Item? = null): TypeItem 95 96 fun convertTypeString(replacementMap: Map<String, String>?): String { 97 return convertTypeString(toTypeString(outerAnnotations = true, innerAnnotations = true), replacementMap) 98 } 99 100 fun isJavaLangObject(): Boolean { 101 return toTypeString() == JAVA_LANG_OBJECT 102 } 103 104 fun defaultValue(): Any? { 105 return when (toTypeString()) { 106 "boolean" -> false 107 "byte" -> 0.toByte() 108 "char" -> '\u0000' 109 "short" -> 0.toShort() 110 "int" -> 0 111 "long" -> 0L 112 "float" -> 0f 113 "double" -> 0.0 114 else -> null 115 } 116 } 117 118 /** Returns true if this type references a type not matched by the given predicate */ 119 fun referencesExcludedType(filter: Predicate<Item>): Boolean { 120 if (primitive) { 121 return false 122 } 123 124 for (item in typeArgumentClasses()) { 125 if (!filter.test(item)) { 126 return true 127 } 128 } 129 130 return false 131 } 132 133 fun defaultValueString(): String = defaultValue()?.toString() ?: "null" 134 135 fun hasTypeArguments(): Boolean = toTypeString().contains("<") 136 137 /** 138 * If this type is a type parameter, then return the corresponding [TypeParameterItem]. 139 * The optional [context] provides the method or class where this type parameter 140 * appears, and can be used for example to resolve the bounds for a type variable 141 * used in a method that was specified on the class. 142 */ 143 fun asTypeParameter(context: MemberItem? = null): TypeParameterItem? 144 145 /** 146 * Mark nullness annotations in the type as recent. 147 * TODO: This isn't very clean; we should model individual annotations. 148 */ 149 fun markRecent() 150 151 companion object { 152 /** Shortens types, if configured */ 153 fun shortenTypes(type: String): String { 154 if (options.omitCommonPackages) { 155 var cleaned = type 156 if (cleaned.contains("@androidx.annotation.")) { 157 cleaned = cleaned.replace("@androidx.annotation.", "@") 158 } 159 if (cleaned.contains("@android.support.annotation.")) { 160 cleaned = cleaned.replace("@android.support.annotation.", "@") 161 } 162 163 return stripJavaLangPrefix(cleaned) 164 } 165 166 return type 167 } 168 169 /** 170 * Removes java.lang. prefixes from types, unless it's in a subpackage such 171 * as java.lang.reflect. For simplicity we may also leave inner classes 172 * in the java.lang package untouched. 173 * 174 * NOTE: We only remove this from the front of the type; e.g. we'll replace 175 * java.lang.Class<java.lang.String> with Class<java.lang.String>. 176 * This is because the signature parsing of types is not 100% accurate 177 * and we don't want to run into trouble with more complicated generic 178 * type signatures where we end up not mapping the simplified types back 179 * to the real fully qualified type names. 180 */ 181 fun stripJavaLangPrefix(type: String): String { 182 if (type.startsWith(JAVA_LANG_PREFIX)) { 183 // Replacing java.lang is harder, since we don't want to operate in sub packages, 184 // e.g. java.lang.String -> String, but java.lang.reflect.Method -> unchanged 185 val start = JAVA_LANG_PREFIX.length 186 val end = type.length 187 for (index in start until end) { 188 if (type[index] == '<') { 189 return type.substring(start) 190 } else if (type[index] == '.') { 191 return type 192 } 193 } 194 195 return type.substring(start) 196 } 197 198 return type 199 } 200 201 fun formatType(type: String?): String { 202 if (type == null) { 203 return "" 204 } 205 206 var cleaned = type 207 208 if (compatibility.spacesAfterCommas && cleaned.indexOf(',') != -1) { 209 // The compat files have spaces after commas where we normally don't 210 cleaned = cleaned.replace(",", ", ").replace(", ", ", ") 211 } 212 213 cleaned = cleanupGenerics(cleaned) 214 return cleaned 215 } 216 217 fun cleanupGenerics(signature: String): String { 218 // <T extends java.lang.Object> is the same as <T> 219 // but NOT for <T extends Object & java.lang.Comparable> -- you can't 220 // shorten this to <T & java.lang.Comparable 221 // return type.replace(" extends java.lang.Object", "") 222 return signature.replace(" extends java.lang.Object>", ">") 223 } 224 225 val comparator: Comparator<TypeItem> = Comparator { type1, type2 -> 226 val cls1 = type1.asClass() 227 val cls2 = type2.asClass() 228 if (cls1 != null && cls2 != null) { 229 ClassItem.fullNameComparator.compare(cls1, cls2) 230 } else { 231 type1.toTypeString().compareTo(type2.toTypeString()) 232 } 233 } 234 235 fun convertTypeString(typeString: String, replacementMap: Map<String, String>?): String { 236 var string = typeString 237 if (replacementMap != null && replacementMap.isNotEmpty()) { 238 // This is a moved method (typically an implementation of an interface 239 // method provided in a hidden superclass), with generics signatures. 240 // We need to rewrite the generics variables in case they differ 241 // between the classes. 242 if (!replacementMap.isEmpty()) { 243 replacementMap.forEach { from, to -> 244 // We can't just replace one string at a time: 245 // what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C? 246 // If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions 247 // simultaneously I get B,C,C. Therefore, we insert "___" as a magical prefix to prevent 248 // scenarios like this, and then we'll drop them afterwards. 249 string = string.replace(Regex(pattern = """\b$from\b"""), replacement = "___$to") 250 } 251 } 252 string = string.replace("___", "") 253 return string 254 } else { 255 return string 256 } 257 } 258 259 // Copied from doclava1 260 fun toSlashFormat(typeName: String): String { 261 var name = typeName 262 var dimension = "" 263 while (name.endsWith("[]")) { 264 dimension += "[" 265 name = name.substring(0, name.length - 2) 266 } 267 268 val base: String 269 base = when (name) { 270 "void" -> "V" 271 "byte" -> "B" 272 "boolean" -> "Z" 273 "char" -> "C" 274 "short" -> "S" 275 "int" -> "I" 276 "long" -> "L" 277 "float" -> "F" 278 "double" -> "D" 279 else -> "L" + ClassContext.getInternalName(name) + ";" 280 } 281 282 return dimension + base 283 } 284 } 285 }