• 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.doclava1.Errors
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(Errors.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(Errors.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             val resolved = expression.resolve()
183             when (resolved) {
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                                         Errors.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 (value) {
419                         Float.POSITIVE_INFINITY -> "(1.0f/0.0f)"
420                         Float.NEGATIVE_INFINITY -> "(-1.0f/0.0f)"
421                         Float.NaN -> "(0.0f/0.0f)"
422                         else -> {
423                             canonicalizeFloatingPointString(value.toString()) + "f"
424                         }
425                     }
426                 }
427                 is Double -> {
428                     return when (value) {
429                         Double.POSITIVE_INFINITY -> "(1.0/0.0)"
430                         Double.NEGATIVE_INFINITY -> "(-1.0/0.0)"
431                         Double.NaN -> "(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 kotlin.Pair<*, *> -> {
442                     val first = value.first
443                     if (first is ClassId) {
444                         return first.packageFqName.asString() + "." + first.relativeClassName.asString()
445                     }
446                 }
447             }
448 
449             return value.toString()
450         }
451 
constantToExpressionnull452         fun constantToExpression(constant: Any?): String? {
453             return when (constant) {
454                 is Int -> "0x${Integer.toHexString(constant)}"
455                 is String -> "\"${javaEscapeString(constant)}\""
456                 is Long -> "${constant}L"
457                 is Boolean -> constant.toString()
458                 is Byte -> Integer.toHexString(constant.toInt())
459                 is Short -> Integer.toHexString(constant.toInt())
460                 is Float -> {
461                     when (constant) {
462                         Float.POSITIVE_INFINITY -> "Float.POSITIVE_INFINITY"
463                         Float.NEGATIVE_INFINITY -> "Float.NEGATIVE_INFINITY"
464                         Float.NaN -> "Float.NaN"
465                         else -> {
466                             "${canonicalizeFloatingPointString(constant.toString())}F"
467                         }
468                     }
469                 }
470                 is Double -> {
471                     when (constant) {
472                         Double.POSITIVE_INFINITY -> "Double.POSITIVE_INFINITY"
473                         Double.NEGATIVE_INFINITY -> "Double.NEGATIVE_INFINITY"
474                         Double.NaN -> "Double.NaN"
475                         else -> {
476                             canonicalizeFloatingPointString(constant.toString())
477                         }
478                     }
479                 }
480                 is Char -> {
481                     "'${javaEscapeString(constant.toString())}'"
482                 }
483                 else -> {
484                     null
485                 }
486             }
487         }
488 
appendSourceLiteralnull489         private fun appendSourceLiteral(v: Any?, sb: StringBuilder, owner: Item): Boolean {
490             if (v == null) {
491                 sb.append("null")
492                 return true
493             }
494             when (v) {
495                 is Int, is Boolean, is Byte, is Short -> {
496                     sb.append(v.toString())
497                     return true
498                 }
499                 is Long -> {
500                     sb.append(v.toString()).append('L')
501                     return true
502                 }
503                 is String -> {
504                     sb.append('"').append(javaEscapeString(v)).append('"')
505                     return true
506                 }
507                 is Float -> {
508                     return when (v) {
509                         Float.POSITIVE_INFINITY -> {
510                             // This convention (displaying fractions) is inherited from doclava
511                             sb.append("(1.0f/0.0f)"); true
512                         }
513                         Float.NEGATIVE_INFINITY -> {
514                             sb.append("(-1.0f/0.0f)"); true
515                         }
516                         Float.NaN -> {
517                             sb.append("(0.0f/0.0f)"); true
518                         }
519                         else -> {
520                             sb.append(canonicalizeFloatingPointString(v.toString()) + "f")
521                             true
522                         }
523                     }
524                 }
525                 is Double -> {
526                     return when (v) {
527                         Double.POSITIVE_INFINITY -> {
528                             // This convention (displaying fractions) is inherited from doclava
529                             sb.append("(1.0/0.0)"); true
530                         }
531                         Double.NEGATIVE_INFINITY -> {
532                             sb.append("(-1.0/0.0)"); true
533                         }
534                         Double.NaN -> {
535                             sb.append("(0.0/0.0)"); true
536                         }
537                         else -> {
538                             sb.append(canonicalizeFloatingPointString(v.toString()))
539                             true
540                         }
541                     }
542                 }
543                 is Char -> {
544                     sb.append('\'').append(javaEscapeString(v.toString())).append('\'')
545                     return true
546                 }
547                 else -> {
548                     reporter.report(Errors.INTERNAL_ERROR, owner, "Unexpected literal value $v")
549                 }
550             }
551 
552             return false
553         }
554     }
555 }