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