• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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