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.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 (value) {
418                         Float.POSITIVE_INFINITY -> "(1.0f/0.0f)"
419                         Float.NEGATIVE_INFINITY -> "(-1.0f/0.0f)"
420                         Float.NaN -> "(0.0f/0.0f)"
421                         else -> {
422                             canonicalizeFloatingPointString(value.toString()) + "f"
423                         }
424                     }
425                 }
426                 is Double -> {
427                     return when (value) {
428                         Double.POSITIVE_INFINITY -> "(1.0/0.0)"
429                         Double.NEGATIVE_INFINITY -> "(-1.0/0.0)"
430                         Double.NaN -> "(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 (constant) {
461                         Float.POSITIVE_INFINITY -> "Float.POSITIVE_INFINITY"
462                         Float.NEGATIVE_INFINITY -> "Float.NEGATIVE_INFINITY"
463                         Float.NaN -> "Float.NaN"
464                         else -> {
465                             "${canonicalizeFloatingPointString(constant.toString())}F"
466                         }
467                     }
468                 }
469                 is Double -> {
470                     when (constant) {
471                         Double.POSITIVE_INFINITY -> "Double.POSITIVE_INFINITY"
472                         Double.NEGATIVE_INFINITY -> "Double.NEGATIVE_INFINITY"
473                         Double.NaN -> "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 (v) {
508                         Float.POSITIVE_INFINITY -> {
509                             // This convention (displaying fractions) is inherited from doclava
510                             sb.append("(1.0f/0.0f)"); true
511                         }
512                         Float.NEGATIVE_INFINITY -> {
513                             sb.append("(-1.0f/0.0f)"); true
514                         }
515                         Float.NaN -> {
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 (v) {
526                         Double.POSITIVE_INFINITY -> {
527                             // This convention (displaying fractions) is inherited from doclava
528                             sb.append("(1.0/0.0)"); true
529                         }
530                         Double.NEGATIVE_INFINITY -> {
531                             sb.append("(-1.0/0.0)"); true
532                         }
533                         Double.NaN -> {
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