1 /* 2 * Copyright (C) 2018 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.psi 18 19 import com.android.SdkConstants.DOT_CLASS 20 import com.android.tools.lint.detector.api.ConstantEvaluator 21 import com.android.tools.metalava.doclava1.Errors 22 import com.android.tools.metalava.model.Codebase 23 import com.android.tools.metalava.model.Item 24 import com.android.tools.metalava.model.canonicalizeFloatingPointString 25 import com.android.tools.metalava.model.javaEscapeString 26 import com.android.tools.metalava.reporter 27 import com.android.utils.XmlUtils 28 import com.intellij.psi.PsiAnnotation 29 import com.intellij.psi.PsiAnnotationMemberValue 30 import com.intellij.psi.PsiArrayInitializerMemberValue 31 import com.intellij.psi.PsiClass 32 import com.intellij.psi.PsiClassObjectAccessExpression 33 import com.intellij.psi.PsiElement 34 import com.intellij.psi.PsiField 35 import com.intellij.psi.PsiLiteral 36 import com.intellij.psi.PsiReference 37 import com.intellij.psi.PsiTypeCastExpression 38 import com.intellij.psi.PsiVariable 39 import org.jetbrains.kotlin.name.ClassId 40 import org.jetbrains.uast.UAnnotation 41 import org.jetbrains.uast.UBinaryExpression 42 import org.jetbrains.uast.UBinaryExpressionWithType 43 import org.jetbrains.uast.UBlockExpression 44 import org.jetbrains.uast.UCallExpression 45 import org.jetbrains.uast.UElement 46 import org.jetbrains.uast.UExpression 47 import org.jetbrains.uast.ULambdaExpression 48 import org.jetbrains.uast.ULiteralExpression 49 import org.jetbrains.uast.UReferenceExpression 50 import org.jetbrains.uast.UUnaryExpression 51 import org.jetbrains.uast.util.isArrayInitializer 52 import org.jetbrains.uast.util.isConstructorCall 53 import org.jetbrains.uast.util.isTypeCast 54 import java.util.function.Predicate 55 56 /** Utility methods */ 57 open class CodePrinter( 58 private val codebase: Codebase, 59 /** Whether we should inline the values of fields, e.g. instead of "Integer.MAX_VALUE" we'd emit "0x7fffffff" */ 60 private val inlineFieldValues: Boolean = true, 61 /** Whether we should inline constants when possible, e.g. instead of "2*20+2" we'd emit "42" */ 62 private val inlineConstants: Boolean = true, 63 /** Whether we should drop unknown AST nodes instead of inserting the corresponding source text strings */ 64 private val skipUnknown: Boolean = false, 65 /** An optional filter to use to determine if we should emit a reference to an item */ 66 private val filterReference: Predicate<Item>? = null 67 ) { warningnull68 open fun warning(message: String, psiElement: PsiElement? = null) { 69 reporter.report(Errors.INTERNAL_ERROR, psiElement, message) 70 } 71 warningnull72 open fun warning(message: String, uElement: UElement) { 73 warning(message, uElement.sourcePsi ?: uElement.javaPsi) 74 } 75 76 /** Given an annotation member value, returns the corresponding Java source expression */ toSourceExpressionnull77 fun toSourceExpression(value: PsiAnnotationMemberValue, owner: Item): String { 78 val sb = StringBuilder() 79 appendSourceExpression(value, sb, owner) 80 return sb.toString() 81 } 82 appendSourceExpressionnull83 private fun appendSourceExpression(value: PsiAnnotationMemberValue, sb: StringBuilder, owner: Item): Boolean { 84 if (value is PsiReference) { 85 val resolved = value.resolve() 86 if (resolved is PsiField) { 87 sb.append(resolved.containingClass?.qualifiedName).append('.').append(resolved.name) 88 return true 89 } 90 } else if (value is PsiLiteral) { 91 return appendSourceLiteral(value.value, sb, owner) 92 } else if (value is PsiClassObjectAccessExpression) { 93 sb.append(value.operand.type.canonicalText).append(DOT_CLASS) 94 return true 95 } else if (value is PsiArrayInitializerMemberValue) { 96 sb.append('{') 97 var first = true 98 val initialLength = sb.length 99 for (e in value.initializers) { 100 val length = sb.length 101 if (first) { 102 first = false 103 } else { 104 sb.append(", ") 105 } 106 val appended = appendSourceExpression(e, sb, owner) 107 if (!appended) { 108 // trunk off comma if it bailed for some reason (e.g. constant 109 // filtered out by API etc) 110 sb.setLength(length) 111 if (length == initialLength) { 112 first = true 113 } 114 } 115 } 116 sb.append('}') 117 return true 118 } else if (value is PsiAnnotation) { 119 sb.append('@').append(value.qualifiedName) 120 return true 121 } else { 122 if (value is PsiTypeCastExpression) { 123 val type = value.castType?.type 124 val operand = value.operand 125 if (type != null && operand is PsiAnnotationMemberValue) { 126 sb.append('(') 127 sb.append(type.canonicalText) 128 sb.append(')') 129 return appendSourceExpression(operand, sb, owner) 130 } 131 } 132 val constant = ConstantEvaluator.evaluate(null, value) 133 if (constant != null) { 134 return appendSourceLiteral(constant, sb, owner) 135 } 136 } 137 reporter.report(Errors.INTERNAL_ERROR, owner, "Unexpected annotation default value $value") 138 return false 139 } 140 toSourceStringnull141 fun toSourceString(value: UExpression?): String? { 142 value ?: return null 143 val sb = StringBuilder() 144 return if (appendExpression(sb, value) 145 ) { 146 sb.toString() 147 } else { 148 null 149 } 150 } 151 appendExpressionnull152 private fun appendExpression( 153 sb: StringBuilder, 154 expression: UExpression 155 ): Boolean { 156 if (expression.isArrayInitializer()) { 157 val call = expression as UCallExpression 158 val initializers = call.valueArguments 159 sb.append('{') 160 var first = true 161 val initialLength = sb.length 162 for (e in initializers) { 163 val length = sb.length 164 if (first) { 165 first = false 166 } else { 167 sb.append(", ") 168 } 169 val appended = appendExpression(sb, e) 170 if (!appended) { 171 // truncate trailing comma if it bailed for some reason (e.g. constant 172 // filtered out by API etc) 173 sb.setLength(length) 174 if (length == initialLength) { 175 first = true 176 } 177 } 178 } 179 sb.append('}') 180 return sb.length != 2 181 } else if (expression is UReferenceExpression) { 182 val resolved = expression.resolve() 183 when (resolved) { 184 is PsiField -> { 185 @Suppress("UnnecessaryVariable") 186 val field = resolved 187 if (!inlineFieldValues) { 188 val value = field.computeConstantValue() 189 if (appendLiteralValue(sb, value)) { 190 return true 191 } 192 } 193 194 val declaringClass = field.containingClass 195 if (declaringClass == null) { 196 warning("No containing class found for " + field.name, field) 197 return false 198 } 199 val qualifiedName = declaringClass.qualifiedName 200 val fieldName = field.name 201 202 if (qualifiedName != null) { 203 if (filterReference != null) { 204 val cls = codebase.findClass(qualifiedName) 205 val fld = cls?.findField(fieldName, true) 206 if (fld == null || !filterReference.test(fld)) { 207 // This field is not visible: remove from typedef 208 if (fld != null) { 209 reporter.report( 210 Errors.HIDDEN_TYPEDEF_CONSTANT, fld, 211 "Typedef class references hidden field $fld: removed from typedef metadata" 212 ) 213 } 214 return false 215 } 216 } 217 sb.append(qualifiedName) 218 sb.append('.') 219 sb.append(fieldName) 220 return true 221 } 222 return if (skipUnknown) { 223 false 224 } else { 225 sb.append(expression.asSourceString()) 226 true 227 } 228 } 229 is PsiVariable -> { 230 sb.append(resolved.name) 231 return true 232 } 233 else -> { 234 if (skipUnknown) { 235 warning("Unexpected reference to $expression", expression) 236 return false 237 } 238 sb.append(expression.asSourceString()) 239 return true 240 } 241 } 242 } else if (expression is ULiteralExpression) { 243 val literalValue = expression.value 244 if (appendLiteralValue(sb, literalValue)) { 245 return true 246 } 247 } else if (expression is UAnnotation) { 248 sb.append('@').append(expression.qualifiedName) 249 return true 250 } else if (expression is UBinaryExpressionWithType) { 251 if ((expression).isTypeCast()) { 252 sb.append('(').append(expression.type.canonicalText).append(')') 253 val operand = expression.operand 254 return appendExpression(sb, operand) 255 } 256 return false 257 } else if (expression is UBinaryExpression) { 258 if (inlineConstants) { 259 val constant = expression.evaluate() 260 if (constant != null) { 261 sb.append(constantToSource(constant)) 262 return true 263 } 264 } 265 266 if (appendExpression(sb, expression.leftOperand)) { 267 sb.append(' ').append(expression.operator.text).append(' ') 268 if (appendExpression(sb, expression.rightOperand)) { 269 return true 270 } 271 } 272 } else if (expression is UUnaryExpression) { 273 sb.append(expression.operator.text) 274 if (appendExpression(sb, expression.operand)) { 275 return true 276 } 277 } else if (expression is ULambdaExpression) { 278 sb.append("{ ") 279 val valueParameters = expression.valueParameters 280 if (valueParameters.isNotEmpty()) { 281 var first = true 282 for (parameter in valueParameters) { 283 if (first) { 284 first = false 285 } else { 286 sb.append(", ") 287 } 288 sb.append(parameter.name) 289 } 290 sb.append(" -> ") 291 } 292 val body = expression.body 293 294 if (body is UBlockExpression) { 295 var first = true 296 for (e in body.expressions) { 297 if (first) { 298 first = false 299 } else { 300 sb.append("; ") 301 } 302 if (!appendExpression(sb, e)) { 303 return false 304 } 305 } 306 307 // Special case: Turn empty lambda { } into {} 308 if (sb.length > 2) { 309 sb.append(' ') 310 } else { 311 sb.setLength(1) 312 } 313 sb.append('}') 314 return true 315 } else { 316 if (appendExpression(sb, body)) { 317 sb.append(" }") 318 return true 319 } 320 } 321 } else if (expression is UBlockExpression) { 322 sb.append('{') 323 var first = true 324 for (e in expression.expressions) { 325 if (first) { 326 first = false 327 } else { 328 sb.append("; ") 329 } 330 if (!appendExpression(sb, e)) { 331 return false 332 } 333 } 334 sb.append('}') 335 return true 336 } else if (expression.isConstructorCall()) { 337 val call = expression as UCallExpression 338 val resolved = call.classReference?.resolve() 339 if (resolved is PsiClass) { 340 sb.append(resolved.qualifiedName) 341 } else { 342 sb.append(call.classReference?.resolvedName) 343 } 344 sb.append('(') 345 var first = true 346 for (arg in call.valueArguments) { 347 if (first) { 348 first = false 349 } else { 350 sb.append(", ") 351 } 352 if (!appendExpression(sb, arg)) { 353 return false 354 } 355 } 356 sb.append(')') 357 return true 358 } else { 359 sb.append(expression.asSourceString()) 360 return true 361 } 362 363 // For example, binary expressions like 3 + 4 364 val literalValue = ConstantEvaluator.evaluate(null, expression) 365 if (literalValue != null) { 366 if (appendLiteralValue(sb, literalValue)) { 367 return true 368 } 369 } 370 371 warning("Unexpected annotation expression of type ${expression.javaClass} and is $expression", expression) 372 373 return false 374 } 375 376 companion object { appendLiteralValuenull377 private fun appendLiteralValue(sb: StringBuilder, literalValue: Any?): Boolean { 378 if (literalValue == null) { 379 sb.append("null") 380 return true 381 } else if (literalValue is Number || literalValue is Boolean) { 382 sb.append(literalValue.toString()) 383 return true 384 } else if (literalValue is String || literalValue is Char) { 385 sb.append('"') 386 XmlUtils.appendXmlAttributeValue(sb, literalValue.toString()) 387 sb.append('"') 388 return true 389 } 390 return false 391 } 392 constantToSourcenull393 fun constantToSource(value: Any?): String { 394 if (value == null) { 395 return "null" 396 } 397 398 when (value) { 399 is Int -> { 400 return value.toString() 401 } 402 is String -> { 403 return "\"${javaEscapeString(value)}\"" 404 } 405 is Long -> { 406 return value.toString() + "L" 407 } 408 is Boolean -> { 409 return value.toString() 410 } 411 is Byte -> { 412 return value.toString() 413 } 414 is Short -> { 415 return value.toString() 416 } 417 is Float -> { 418 return when (value) { 419 Float.POSITIVE_INFINITY -> "(1.0f/0.0f)" 420 Float.NEGATIVE_INFINITY -> "(-1.0f/0.0f)" 421 Float.NaN -> "(0.0f/0.0f)" 422 else -> { 423 canonicalizeFloatingPointString(value.toString()) + "f" 424 } 425 } 426 } 427 is Double -> { 428 return when (value) { 429 Double.POSITIVE_INFINITY -> "(1.0/0.0)" 430 Double.NEGATIVE_INFINITY -> "(-1.0/0.0)" 431 Double.NaN -> "(0.0/0.0)" 432 else -> { 433 canonicalizeFloatingPointString(value.toString()) 434 } 435 } 436 } 437 is Char -> { 438 return String.format("'%s'", javaEscapeString(value.toString())) 439 } 440 441 is kotlin.Pair<*, *> -> { 442 val first = value.first 443 if (first is ClassId) { 444 return first.packageFqName.asString() + "." + first.relativeClassName.asString() 445 } 446 } 447 } 448 449 return value.toString() 450 } 451 constantToExpressionnull452 fun constantToExpression(constant: Any?): String? { 453 return when (constant) { 454 is Int -> "0x${Integer.toHexString(constant)}" 455 is String -> "\"${javaEscapeString(constant)}\"" 456 is Long -> "${constant}L" 457 is Boolean -> constant.toString() 458 is Byte -> Integer.toHexString(constant.toInt()) 459 is Short -> Integer.toHexString(constant.toInt()) 460 is Float -> { 461 when (constant) { 462 Float.POSITIVE_INFINITY -> "Float.POSITIVE_INFINITY" 463 Float.NEGATIVE_INFINITY -> "Float.NEGATIVE_INFINITY" 464 Float.NaN -> "Float.NaN" 465 else -> { 466 "${canonicalizeFloatingPointString(constant.toString())}F" 467 } 468 } 469 } 470 is Double -> { 471 when (constant) { 472 Double.POSITIVE_INFINITY -> "Double.POSITIVE_INFINITY" 473 Double.NEGATIVE_INFINITY -> "Double.NEGATIVE_INFINITY" 474 Double.NaN -> "Double.NaN" 475 else -> { 476 canonicalizeFloatingPointString(constant.toString()) 477 } 478 } 479 } 480 is Char -> { 481 "'${javaEscapeString(constant.toString())}'" 482 } 483 else -> { 484 null 485 } 486 } 487 } 488 appendSourceLiteralnull489 private fun appendSourceLiteral(v: Any?, sb: StringBuilder, owner: Item): Boolean { 490 if (v == null) { 491 sb.append("null") 492 return true 493 } 494 when (v) { 495 is Int, is Boolean, is Byte, is Short -> { 496 sb.append(v.toString()) 497 return true 498 } 499 is Long -> { 500 sb.append(v.toString()).append('L') 501 return true 502 } 503 is String -> { 504 sb.append('"').append(javaEscapeString(v)).append('"') 505 return true 506 } 507 is Float -> { 508 return when (v) { 509 Float.POSITIVE_INFINITY -> { 510 // This convention (displaying fractions) is inherited from doclava 511 sb.append("(1.0f/0.0f)"); true 512 } 513 Float.NEGATIVE_INFINITY -> { 514 sb.append("(-1.0f/0.0f)"); true 515 } 516 Float.NaN -> { 517 sb.append("(0.0f/0.0f)"); true 518 } 519 else -> { 520 sb.append(canonicalizeFloatingPointString(v.toString()) + "f") 521 true 522 } 523 } 524 } 525 is Double -> { 526 return when (v) { 527 Double.POSITIVE_INFINITY -> { 528 // This convention (displaying fractions) is inherited from doclava 529 sb.append("(1.0/0.0)"); true 530 } 531 Double.NEGATIVE_INFINITY -> { 532 sb.append("(-1.0/0.0)"); true 533 } 534 Double.NaN -> { 535 sb.append("(0.0/0.0)"); true 536 } 537 else -> { 538 sb.append(canonicalizeFloatingPointString(v.toString())) 539 true 540 } 541 } 542 } 543 is Char -> { 544 sb.append('\'').append(javaEscapeString(v.toString())).append('\'') 545 return true 546 } 547 else -> { 548 reporter.report(Errors.INTERNAL_ERROR, owner, "Unexpected literal value $v") 549 } 550 } 551 552 return false 553 } 554 } 555 }