1 /* 2 * 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.text 18 19 import com.android.tools.metalava.JAVA_LANG_OBJECT 20 import com.android.tools.metalava.JAVA_LANG_PREFIX 21 import com.android.tools.metalava.doclava1.TextCodebase 22 import com.android.tools.metalava.model.AnnotationItem 23 import com.android.tools.metalava.model.ClassItem 24 import com.android.tools.metalava.model.Item 25 import com.android.tools.metalava.model.MemberItem 26 import com.android.tools.metalava.model.MethodItem 27 import com.android.tools.metalava.model.TypeItem 28 import com.android.tools.metalava.model.TypeParameterItem 29 import com.android.tools.metalava.model.TypeParameterList 30 import com.android.tools.metalava.model.TypeParameterListOwner 31 import java.util.function.Predicate 32 33 const val ASSUME_TYPE_VARS_EXTEND_OBJECT = false 34 35 class TextTypeItem( 36 val codebase: TextCodebase, 37 val type: String 38 ) : TypeItem { 39 toStringnull40 override fun toString(): String = type 41 42 override fun toErasedTypeString(context: Item?): String { 43 return toTypeString( 44 outerAnnotations = false, 45 innerAnnotations = false, 46 erased = true, 47 kotlinStyleNulls = false, 48 context = context 49 ) 50 } 51 toTypeStringnull52 override fun toTypeString( 53 outerAnnotations: Boolean, 54 innerAnnotations: Boolean, 55 erased: Boolean, 56 kotlinStyleNulls: Boolean, 57 context: Item?, 58 filter: Predicate<Item>? 59 ): String { 60 val typeString = toTypeString(type, outerAnnotations, innerAnnotations, erased, context) 61 62 if (innerAnnotations && kotlinStyleNulls && !primitive && context != null) { 63 var nullable: Boolean? = AnnotationItem.getImplicitNullness(context) 64 65 if (nullable == null) { 66 for (annotation in context.modifiers.annotations()) { 67 if (annotation.isNullable()) { 68 nullable = true 69 } else if (annotation.isNonNull()) { 70 nullable = false 71 } 72 } 73 } 74 when (nullable) { 75 null -> return "$typeString!" 76 true -> return "$typeString?" 77 // else: non-null: nothing to add 78 } 79 } 80 return typeString 81 } 82 asClassnull83 override fun asClass(): ClassItem? { 84 if (primitive) { 85 return null 86 } 87 val cls = run { 88 val erased = toErasedTypeString() 89 // Also chop off array dimensions 90 val index = erased.indexOf('[') 91 if (index != -1) { 92 erased.substring(0, index) 93 } else { 94 erased 95 } 96 } 97 return codebase.getOrCreateClass(cls) 98 } 99 qualifiedTypeNamenull100 fun qualifiedTypeName(): String = type 101 102 override fun equals(other: Any?): Boolean { 103 if (this === other) return true 104 105 return when (other) { 106 // Note: when we support type-use annotations, this is not safe: there could be a string 107 // literal inside which is significant 108 is TextTypeItem -> TypeItem.equalsWithoutSpace(toString(), other.toString()) 109 is TypeItem -> { 110 val thisString = toTypeString() 111 val otherString = other.toTypeString() 112 if (TypeItem.equalsWithoutSpace(thisString, otherString)) { 113 return true 114 } 115 if (thisString.startsWith(JAVA_LANG_PREFIX) && thisString.endsWith(otherString) && 116 thisString.length == otherString.length + JAVA_LANG_PREFIX.length 117 ) { 118 // When reading signature files, it's sometimes ambiguous whether a name 119 // references a java.lang. implicit class or a type parameter. 120 return true 121 } 122 123 return false 124 } 125 else -> false 126 } 127 } 128 hashCodenull129 override fun hashCode(): Int { 130 return qualifiedTypeName().hashCode() 131 } 132 arrayDimensionsnull133 override fun arrayDimensions(): Int { 134 val type = toErasedTypeString() 135 var dimensions = 0 136 for (c in type) { 137 if (c == '[') { 138 dimensions++ 139 } 140 } 141 return dimensions 142 } 143 findTypeVariableBoundsnull144 private fun findTypeVariableBounds(typeParameterList: TypeParameterList, name: String): List<ClassItem> { 145 for (p in typeParameterList.typeParameters()) { 146 if (p.simpleName() == name) { 147 val bounds = p.bounds() 148 if (bounds.isNotEmpty()) { 149 return bounds 150 } 151 } 152 } 153 154 return emptyList() 155 } 156 findTypeVariableBoundsnull157 private fun findTypeVariableBounds(context: Item?, name: String): List<ClassItem> { 158 if (context is MethodItem) { 159 val bounds = findTypeVariableBounds(context.typeParameterList(), name) 160 if (bounds.isNotEmpty()) { 161 return bounds 162 } 163 return findTypeVariableBounds(context.containingClass().typeParameterList(), name) 164 } else if (context is ClassItem) { 165 return findTypeVariableBounds(context.typeParameterList(), name) 166 } 167 168 return emptyList() 169 } 170 asTypeParameternull171 override fun asTypeParameter(context: MemberItem?): TypeParameterItem? { 172 return if (isLikelyTypeParameter(toTypeString())) { 173 val typeParameter = 174 TextTypeParameterItem.create(codebase, context as? TypeParameterListOwner, toTypeString()) 175 176 if (context != null && typeParameter.bounds().isEmpty()) { 177 val bounds = findTypeVariableBounds(context, typeParameter.simpleName()) 178 if (bounds.isNotEmpty()) { 179 val filtered = bounds.filter { !it.isJavaLangObject() } 180 if (filtered.isNotEmpty()) { 181 return TextTypeParameterItem.create( 182 codebase, 183 context as? TypeParameterListOwner, 184 toTypeString(), 185 bounds 186 ) 187 } 188 } 189 } 190 191 typeParameter 192 } else { 193 null 194 } 195 } 196 197 override val primitive: Boolean 198 get() = isPrimitive(type) 199 typeArgumentClassesnull200 override fun typeArgumentClasses(): List<ClassItem> = codebase.unsupported() 201 202 override fun convertType(replacementMap: Map<String, String>?, owner: Item?): TypeItem { 203 return TextTypeItem(codebase, convertTypeString(replacementMap)) 204 } 205 markRecentnull206 override fun markRecent() = codebase.unsupported() 207 208 override fun scrubAnnotations() = codebase.unsupported() 209 210 companion object { 211 // heuristic to guess if a given type parameter is a type variable 212 fun isLikelyTypeParameter(typeString: String): Boolean { 213 val first = typeString[0] 214 if (!Character.isUpperCase((first)) && first != '_') { 215 // This rules out primitives which otherwise don't have 216 return false 217 } 218 for (c in typeString) { 219 if (c == '.') { 220 // This rules out qualified class names 221 return false 222 } 223 if (c == ' ' || c == '[' || c == '<') { 224 return true 225 } 226 // I'd like to check for all uppercase here but there are APIs which 227 // violate this, such as AsyncTask where the type variable names include "Result" 228 } 229 230 return true 231 } 232 233 fun toTypeString( 234 type: String, 235 outerAnnotations: Boolean, 236 innerAnnotations: Boolean, 237 erased: Boolean, 238 context: Item? = null 239 ): String { 240 return if (erased) { 241 val raw = eraseTypeArguments(type) 242 val concrete = substituteTypeParameters(raw, context) 243 if (outerAnnotations && innerAnnotations) { 244 concrete 245 } else { 246 eraseAnnotations(concrete, outerAnnotations, innerAnnotations) 247 } 248 } else { 249 if (outerAnnotations && innerAnnotations) { 250 type 251 } else { 252 eraseAnnotations(type, outerAnnotations, innerAnnotations) 253 } 254 } 255 } 256 257 private fun substituteTypeParameters(s: String, context: Item?): String { 258 if (context is TypeParameterListOwner) { 259 var end = s.indexOf('[') 260 if (end == -1) { 261 end = s.length 262 } 263 if (s[0].isUpperCase() && s.lastIndexOf('.', end) == -1) { 264 val v = s.substring(0, end) 265 val parameter = context.resolveParameter(v) 266 if (parameter != null) { 267 val bounds = parameter.bounds() 268 if (bounds.isNotEmpty()) { 269 return bounds.first().qualifiedName() + s.substring(end) 270 } 271 @Suppress("ConstantConditionIf") 272 if (ASSUME_TYPE_VARS_EXTEND_OBJECT) { 273 return JAVA_LANG_OBJECT + s.substring(end) 274 } 275 } 276 } 277 } 278 279 return s 280 } 281 282 fun eraseTypeArguments(s: String): String { 283 val index = s.indexOf('<') 284 if (index != -1) { 285 var balance = 0 286 for (i in index..s.length) { 287 val c = s[i] 288 if (c == '<') { 289 balance++ 290 } else if (c == '>') { 291 balance-- 292 if (balance == 0) { 293 return if (i == s.length - 1) { 294 s.substring(0, index) 295 } else { 296 s.substring(0, index) + s.substring(i + 1) 297 } 298 } 299 } 300 } 301 302 return s.substring(0, index) 303 } 304 return s 305 } 306 307 private fun eraseAnnotations(type: String, outer: Boolean, inner: Boolean): String { 308 if (type.indexOf('@') == -1) { 309 return type 310 } 311 312 assert(inner || !outer) // Can't supply outer=true,inner=false 313 314 // Assumption: top level annotations appear first 315 val length = type.length 316 var max = if (!inner) 317 length 318 else { 319 val space = type.indexOf(' ') 320 val generics = type.indexOf('<') 321 val first = if (space != -1) { 322 if (generics != -1) { 323 Math.min(space, generics) 324 } else { 325 space 326 } 327 } else { 328 generics 329 } 330 if (first != -1) { 331 first 332 } else { 333 length 334 } 335 } 336 337 var s = type 338 while (true) { 339 val index = s.indexOf('@') 340 if (index == -1 || index >= max) { 341 break 342 } 343 344 // Find end 345 val end = findAnnotationEnd(s, index + 1) 346 val oldLength = s.length 347 s = s.substring(0, index).trim() + s.substring(end).trim() 348 val newLength = s.length 349 val removed = oldLength - newLength 350 max -= removed 351 } 352 353 // Sometimes we have a second type after the max, such as 354 // @androidx.annotation.NonNull java.lang.reflect.@androidx.annotation.NonNull TypeVariable<...> 355 for (i in 0 until s.length) { 356 val c = s[i] 357 if (Character.isJavaIdentifierPart(c) || c == '.') { 358 continue 359 } else if (c == '@') { 360 // Found embedded annotation within the type 361 val end = findAnnotationEnd(s, i + 1) 362 if (end == -1 || end == length) { 363 break 364 } 365 366 s = s.substring(0, i).trim() + s.substring(end).trim() 367 break 368 } else { 369 break 370 } 371 } 372 373 return s 374 } 375 376 private fun findAnnotationEnd(type: String, start: Int): Int { 377 var index = start 378 val length = type.length 379 var balance = 0 380 while (index < length) { 381 val c = type[index] 382 if (c == '(') { 383 balance++ 384 } else if (c == ')') { 385 balance-- 386 if (balance == 0) { 387 return index + 1 388 } 389 } else if (c == '.') { 390 } else if (Character.isJavaIdentifierPart(c)) { 391 } else if (balance == 0) { 392 break 393 } 394 index++ 395 } 396 return index 397 } 398 399 fun isPrimitive(type: String): Boolean { 400 return when (type) { 401 "byte", "char", "double", "float", "int", "long", "short", "boolean", "void", "null" -> true 402 else -> false 403 } 404 } 405 } 406 }