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.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(Issues.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(Issues.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 when (val resolved = expression.resolve()) { 183 is PsiField -> { 184 @Suppress("UnnecessaryVariable") 185 val field = resolved 186 if (!inlineFieldValues) { 187 val value = field.computeConstantValue() 188 if (appendLiteralValue(sb, value)) { 189 return true 190 } 191 } 192 193 val declaringClass = field.containingClass 194 if (declaringClass == null) { 195 warning("No containing class found for " + field.name, field) 196 return false 197 } 198 val qualifiedName = declaringClass.qualifiedName 199 val fieldName = field.name 200 201 if (qualifiedName != null) { 202 if (filterReference != null) { 203 val cls = codebase.findClass(qualifiedName) 204 val fld = cls?.findField(fieldName, true) 205 if (fld == null || !filterReference.test(fld)) { 206 // This field is not visible: remove from typedef 207 if (fld != null) { 208 reporter.report( 209 Issues.HIDDEN_TYPEDEF_CONSTANT, fld, 210 "Typedef class references hidden field $fld: removed from typedef metadata" 211 ) 212 } 213 return false 214 } 215 } 216 sb.append(qualifiedName) 217 sb.append('.') 218 sb.append(fieldName) 219 return true 220 } 221 return if (skipUnknown) { 222 false 223 } else { 224 sb.append(expression.asSourceString()) 225 true 226 } 227 } 228 is PsiVariable -> { 229 sb.append(resolved.name) 230 return true 231 } 232 else -> { 233 if (skipUnknown) { 234 warning("Unexpected reference to $expression", expression) 235 return false 236 } 237 sb.append(expression.asSourceString()) 238 return true 239 } 240 } 241 } else if (expression is ULiteralExpression) { 242 val literalValue = expression.value 243 if (appendLiteralValue(sb, literalValue)) { 244 return true 245 } 246 } else if (expression is UAnnotation) { 247 sb.append('@').append(expression.qualifiedName) 248 return true 249 } else if (expression is UBinaryExpressionWithType) { 250 if ((expression).isTypeCast()) { 251 sb.append('(').append(expression.type.canonicalText).append(')') 252 val operand = expression.operand 253 return appendExpression(sb, operand) 254 } 255 return false 256 } else if (expression is UBinaryExpression) { 257 if (inlineConstants) { 258 val constant = expression.evaluate() 259 if (constant != null) { 260 sb.append(constantToSource(constant)) 261 return true 262 } 263 } 264 265 if (appendExpression(sb, expression.leftOperand)) { 266 sb.append(' ').append(expression.operator.text).append(' ') 267 if (appendExpression(sb, expression.rightOperand)) { 268 return true 269 } 270 } 271 } else if (expression is UUnaryExpression) { 272 sb.append(expression.operator.text) 273 if (appendExpression(sb, expression.operand)) { 274 return true 275 } 276 } else if (expression is ULambdaExpression) { 277 sb.append("{ ") 278 val valueParameters = expression.valueParameters 279 if (valueParameters.isNotEmpty()) { 280 var first = true 281 for (parameter in valueParameters) { 282 if (first) { 283 first = false 284 } else { 285 sb.append(", ") 286 } 287 sb.append(parameter.name) 288 } 289 sb.append(" -> ") 290 } 291 val body = expression.body 292 293 if (body is UBlockExpression) { 294 var first = true 295 for (e in body.expressions) { 296 if (first) { 297 first = false 298 } else { 299 sb.append("; ") 300 } 301 if (!appendExpression(sb, e)) { 302 return false 303 } 304 } 305 306 // Special case: Turn empty lambda { } into {} 307 if (sb.length > 2) { 308 sb.append(' ') 309 } else { 310 sb.setLength(1) 311 } 312 sb.append('}') 313 return true 314 } else { 315 if (appendExpression(sb, body)) { 316 sb.append(" }") 317 return true 318 } 319 } 320 } else if (expression is UBlockExpression) { 321 sb.append('{') 322 var first = true 323 for (e in expression.expressions) { 324 if (first) { 325 first = false 326 } else { 327 sb.append("; ") 328 } 329 if (!appendExpression(sb, e)) { 330 return false 331 } 332 } 333 sb.append('}') 334 return true 335 } else if (expression.isConstructorCall()) { 336 val call = expression as UCallExpression 337 val resolved = call.classReference?.resolve() 338 if (resolved is PsiClass) { 339 sb.append(resolved.qualifiedName) 340 } else { 341 sb.append(call.classReference?.resolvedName) 342 } 343 sb.append('(') 344 var first = true 345 for (arg in call.valueArguments) { 346 if (first) { 347 first = false 348 } else { 349 sb.append(", ") 350 } 351 if (!appendExpression(sb, arg)) { 352 return false 353 } 354 } 355 sb.append(')') 356 return true 357 } else { 358 sb.append(expression.asSourceString()) 359 return true 360 } 361 362 // For example, binary expressions like 3 + 4 363 val literalValue = ConstantEvaluator.evaluate(null, expression) 364 if (literalValue != null) { 365 if (appendLiteralValue(sb, literalValue)) { 366 return true 367 } 368 } 369 370 warning("Unexpected annotation expression of type ${expression.javaClass} and is $expression", expression) 371 372 return false 373 } 374 375 companion object { appendLiteralValuenull376 private fun appendLiteralValue(sb: StringBuilder, literalValue: Any?): Boolean { 377 if (literalValue == null) { 378 sb.append("null") 379 return true 380 } else if (literalValue is Number || literalValue is Boolean) { 381 sb.append(literalValue.toString()) 382 return true 383 } else if (literalValue is String || literalValue is Char) { 384 sb.append('"') 385 XmlUtils.appendXmlAttributeValue(sb, literalValue.toString()) 386 sb.append('"') 387 return true 388 } 389 return false 390 } 391 constantToSourcenull392 fun constantToSource(value: Any?): String { 393 if (value == null) { 394 return "null" 395 } 396 397 when (value) { 398 is Int -> { 399 return value.toString() 400 } 401 is String -> { 402 return "\"${javaEscapeString(value)}\"" 403 } 404 is Long -> { 405 return value.toString() + "L" 406 } 407 is Boolean -> { 408 return value.toString() 409 } 410 is Byte -> { 411 return value.toString() 412 } 413 is Short -> { 414 return value.toString() 415 } 416 is Float -> { 417 return when { 418 value == Float.POSITIVE_INFINITY -> "(1.0f/0.0f)" 419 value == Float.NEGATIVE_INFINITY -> "(-1.0f/0.0f)" 420 java.lang.Float.isNaN(value) -> "(0.0f/0.0f)" 421 else -> { 422 canonicalizeFloatingPointString(value.toString()) + "f" 423 } 424 } 425 } 426 is Double -> { 427 return when { 428 value == Double.POSITIVE_INFINITY -> "(1.0/0.0)" 429 value == Double.NEGATIVE_INFINITY -> "(-1.0/0.0)" 430 java.lang.Double.isNaN(value) -> "(0.0/0.0)" 431 else -> { 432 canonicalizeFloatingPointString(value.toString()) 433 } 434 } 435 } 436 is Char -> { 437 return String.format("'%s'", javaEscapeString(value.toString())) 438 } 439 440 is Pair<*, *> -> { 441 val first = value.first 442 if (first is ClassId) { 443 return first.packageFqName.asString() + "." + first.relativeClassName.asString() 444 } 445 } 446 } 447 448 return value.toString() 449 } 450 constantToExpressionnull451 fun constantToExpression(constant: Any?): String? { 452 return when (constant) { 453 is Int -> "0x${Integer.toHexString(constant)}" 454 is String -> "\"${javaEscapeString(constant)}\"" 455 is Long -> "${constant}L" 456 is Boolean -> constant.toString() 457 is Byte -> Integer.toHexString(constant.toInt()) 458 is Short -> Integer.toHexString(constant.toInt()) 459 is Float -> { 460 when { 461 constant == Float.POSITIVE_INFINITY -> "Float.POSITIVE_INFINITY" 462 constant == Float.NEGATIVE_INFINITY -> "Float.NEGATIVE_INFINITY" 463 java.lang.Float.isNaN(constant) -> "Float.NaN" 464 else -> { 465 "${canonicalizeFloatingPointString(constant.toString())}F" 466 } 467 } 468 } 469 is Double -> { 470 when { 471 constant == Double.POSITIVE_INFINITY -> "Double.POSITIVE_INFINITY" 472 constant == Double.NEGATIVE_INFINITY -> "Double.NEGATIVE_INFINITY" 473 java.lang.Double.isNaN(constant) -> "Double.NaN" 474 else -> { 475 canonicalizeFloatingPointString(constant.toString()) 476 } 477 } 478 } 479 is Char -> { 480 "'${javaEscapeString(constant.toString())}'" 481 } 482 else -> { 483 null 484 } 485 } 486 } 487 appendSourceLiteralnull488 private fun appendSourceLiteral(v: Any?, sb: StringBuilder, owner: Item): Boolean { 489 if (v == null) { 490 sb.append("null") 491 return true 492 } 493 when (v) { 494 is Int, is Boolean, is Byte, is Short -> { 495 sb.append(v.toString()) 496 return true 497 } 498 is Long -> { 499 sb.append(v.toString()).append('L') 500 return true 501 } 502 is String -> { 503 sb.append('"').append(javaEscapeString(v)).append('"') 504 return true 505 } 506 is Float -> { 507 return when { 508 v == Float.POSITIVE_INFINITY -> { 509 // This convention (displaying fractions) is inherited from doclava 510 sb.append("(1.0f/0.0f)"); true 511 } 512 v == Float.NEGATIVE_INFINITY -> { 513 sb.append("(-1.0f/0.0f)"); true 514 } 515 java.lang.Float.isNaN(v) -> { 516 sb.append("(0.0f/0.0f)"); true 517 } 518 else -> { 519 sb.append(canonicalizeFloatingPointString(v.toString()) + "f") 520 true 521 } 522 } 523 } 524 is Double -> { 525 return when { 526 v == Double.POSITIVE_INFINITY -> { 527 // This convention (displaying fractions) is inherited from doclava 528 sb.append("(1.0/0.0)"); true 529 } 530 v == Double.NEGATIVE_INFINITY -> { 531 sb.append("(-1.0/0.0)"); true 532 } 533 java.lang.Double.isNaN(v) -> { 534 sb.append("(0.0/0.0)"); true 535 } 536 else -> { 537 sb.append(canonicalizeFloatingPointString(v.toString())) 538 true 539 } 540 } 541 } 542 is Char -> { 543 sb.append('\'').append(javaEscapeString(v.toString())).append('\'') 544 return true 545 } 546 else -> { 547 reporter.report(Issues.INTERNAL_ERROR, owner, "Unexpected literal value $v") 548 } 549 } 550 551 return false 552 } 553 } 554 } 555