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.Issues 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.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 import java.util.function.Predicate 56 57 /** Utility methods */ 58 open class CodePrinter( 59 private val codebase: Codebase, 60 /** Whether we should inline the values of fields, e.g. instead of "Integer.MAX_VALUE" we'd emit "0x7fffffff" */ 61 private val inlineFieldValues: Boolean = true, 62 /** Whether we should inline constants when possible, e.g. instead of "2*20+2" we'd emit "42" */ 63 private val inlineConstants: Boolean = true, 64 /** Whether we should drop unknown AST nodes instead of inserting the corresponding source text strings */ 65 private val skipUnknown: Boolean = false, 66 /** An optional filter to use to determine if we should emit a reference to an item */ 67 private val filterReference: Predicate<Item>? = null 68 ) { warningnull69 open fun warning(message: String, psiElement: PsiElement? = null) { 70 reporter.report(Issues.INTERNAL_ERROR, psiElement, message) 71 } 72 warningnull73 open fun warning(message: String, uElement: UElement) { 74 warning(message, uElement.sourcePsi ?: uElement.javaPsi) 75 } 76 77 /** Given an annotation member value, returns the corresponding Java source expression */ toSourceExpressionnull78 fun toSourceExpression(value: PsiAnnotationMemberValue, owner: Item): String { 79 val sb = StringBuilder() 80 appendSourceExpression(value, sb, owner) 81 return sb.toString() 82 } 83 appendSourceExpressionnull84 private fun appendSourceExpression(value: PsiAnnotationMemberValue, sb: StringBuilder, owner: Item): Boolean { 85 if (value is PsiReference) { 86 val resolved = value.resolve() 87 if (resolved is PsiField) { 88 sb.append(resolved.containingClass?.qualifiedName).append('.').append(resolved.name) 89 return true 90 } 91 } else if (value is PsiLiteral) { 92 return appendSourceLiteral(value.value, sb, owner) 93 } else if (value is PsiClassObjectAccessExpression) { 94 sb.append(value.operand.type.canonicalText).append(DOT_CLASS) 95 return true 96 } else if (value is PsiArrayInitializerMemberValue) { 97 sb.append('{') 98 var first = true 99 val initialLength = sb.length 100 for (e in value.initializers) { 101 val length = sb.length 102 if (first) { 103 first = false 104 } else { 105 sb.append(", ") 106 } 107 val appended = appendSourceExpression(e, sb, owner) 108 if (!appended) { 109 // trunk off comma if it bailed for some reason (e.g. constant 110 // filtered out by API etc) 111 sb.setLength(length) 112 if (length == initialLength) { 113 first = true 114 } 115 } 116 } 117 sb.append('}') 118 return true 119 } else if (value is PsiAnnotation) { 120 sb.append('@').append(value.qualifiedName) 121 return true 122 } else { 123 if (value is PsiTypeCastExpression) { 124 val type = value.castType?.type 125 val operand = value.operand 126 if (type != null && operand is PsiAnnotationMemberValue) { 127 sb.append('(') 128 sb.append(type.canonicalText) 129 sb.append(')') 130 return appendSourceExpression(operand, sb, owner) 131 } 132 } 133 val constant = ConstantEvaluator.evaluate(null, value) 134 if (constant != null) { 135 return appendSourceLiteral(constant, sb, owner) 136 } 137 } 138 reporter.report(Issues.INTERNAL_ERROR, owner, "Unexpected annotation default value $value") 139 return false 140 } 141 toSourceStringnull142 fun toSourceString(value: UExpression?): String? { 143 value ?: return null 144 val sb = StringBuilder() 145 return if (appendExpression(sb, value) 146 ) { 147 sb.toString() 148 } else { 149 null 150 } 151 } 152 appendExpressionnull153 private fun appendExpression( 154 sb: StringBuilder, 155 expression: UExpression 156 ): Boolean { 157 if (expression.isArrayInitializer()) { 158 val call = expression as UCallExpression 159 val initializers = call.valueArguments 160 sb.append('{') 161 var first = true 162 val initialLength = sb.length 163 for (e in initializers) { 164 val length = sb.length 165 if (first) { 166 first = false 167 } else { 168 sb.append(", ") 169 } 170 val appended = appendExpression(sb, e) 171 if (!appended) { 172 // truncate trailing comma if it bailed for some reason (e.g. constant 173 // filtered out by API etc) 174 sb.setLength(length) 175 if (length == initialLength) { 176 first = true 177 } 178 } 179 } 180 sb.append('}') 181 return sb.length != 2 182 } else if (expression is UReferenceExpression) { 183 when (val resolved = expression.resolve()) { 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 Issues.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 { 419 value == Float.POSITIVE_INFINITY -> "(1.0f/0.0f)" 420 value == Float.NEGATIVE_INFINITY -> "(-1.0f/0.0f)" 421 java.lang.Float.isNaN(value) -> "(0.0f/0.0f)" 422 else -> { 423 canonicalizeFloatingPointString(value.toString()) + "f" 424 } 425 } 426 } 427 is Double -> { 428 return when { 429 value == Double.POSITIVE_INFINITY -> "(1.0/0.0)" 430 value == Double.NEGATIVE_INFINITY -> "(-1.0/0.0)" 431 java.lang.Double.isNaN(value) -> "(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 Pair<*, *> -> { 442 val first = value.first 443 val second = value.second 444 if (first is ClassId) { 445 val qualifiedName = first.packageFqName.asString() + "." + 446 first.relativeClassName.asString() 447 return if (second is Name) { 448 qualifiedName + "." + second.asString() 449 } else { 450 qualifiedName 451 } 452 } 453 } 454 } 455 456 return value.toString() 457 } 458 constantToExpressionnull459 fun constantToExpression(constant: Any?): String? { 460 return when (constant) { 461 is Int -> "0x${Integer.toHexString(constant)}" 462 is String -> "\"${javaEscapeString(constant)}\"" 463 is Long -> "${constant}L" 464 is Boolean -> constant.toString() 465 is Byte -> Integer.toHexString(constant.toInt()) 466 is Short -> Integer.toHexString(constant.toInt()) 467 is Float -> { 468 when { 469 constant == Float.POSITIVE_INFINITY -> "Float.POSITIVE_INFINITY" 470 constant == Float.NEGATIVE_INFINITY -> "Float.NEGATIVE_INFINITY" 471 java.lang.Float.isNaN(constant) -> "Float.NaN" 472 else -> { 473 "${canonicalizeFloatingPointString(constant.toString())}F" 474 } 475 } 476 } 477 is Double -> { 478 when { 479 constant == Double.POSITIVE_INFINITY -> "Double.POSITIVE_INFINITY" 480 constant == Double.NEGATIVE_INFINITY -> "Double.NEGATIVE_INFINITY" 481 java.lang.Double.isNaN(constant) -> "Double.NaN" 482 else -> { 483 canonicalizeFloatingPointString(constant.toString()) 484 } 485 } 486 } 487 is Char -> { 488 "'${javaEscapeString(constant.toString())}'" 489 } 490 else -> { 491 null 492 } 493 } 494 } 495 appendSourceLiteralnull496 private fun appendSourceLiteral(v: Any?, sb: StringBuilder, owner: Item): Boolean { 497 if (v == null) { 498 sb.append("null") 499 return true 500 } 501 when (v) { 502 is Int, is Boolean, is Byte, is Short -> { 503 sb.append(v.toString()) 504 return true 505 } 506 is Long -> { 507 sb.append(v.toString()).append('L') 508 return true 509 } 510 is String -> { 511 sb.append('"').append(javaEscapeString(v)).append('"') 512 return true 513 } 514 is Float -> { 515 return when { 516 v == Float.POSITIVE_INFINITY -> { 517 // This convention (displaying fractions) is inherited from doclava 518 sb.append("(1.0f/0.0f)"); true 519 } 520 v == Float.NEGATIVE_INFINITY -> { 521 sb.append("(-1.0f/0.0f)"); true 522 } 523 java.lang.Float.isNaN(v) -> { 524 sb.append("(0.0f/0.0f)"); true 525 } 526 else -> { 527 sb.append(canonicalizeFloatingPointString(v.toString()) + "f") 528 true 529 } 530 } 531 } 532 is Double -> { 533 return when { 534 v == Double.POSITIVE_INFINITY -> { 535 // This convention (displaying fractions) is inherited from doclava 536 sb.append("(1.0/0.0)"); true 537 } 538 v == Double.NEGATIVE_INFINITY -> { 539 sb.append("(-1.0/0.0)"); true 540 } 541 java.lang.Double.isNaN(v) -> { 542 sb.append("(0.0/0.0)"); true 543 } 544 else -> { 545 sb.append(canonicalizeFloatingPointString(v.toString())) 546 true 547 } 548 } 549 } 550 is Char -> { 551 sb.append('\'').append(javaEscapeString(v.toString())).append('\'') 552 return true 553 } 554 else -> { 555 reporter.report(Issues.INTERNAL_ERROR, owner, "Unexpected literal value $v") 556 } 557 } 558 559 return false 560 } 561 } 562 } 563