1 /*
<lambda>null2  * Copyright (C) 2019 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.protolog.tool
18 
19 import com.android.internal.protolog.common.LogLevel
20 import com.github.javaparser.ast.CompilationUnit
21 import com.github.javaparser.ast.expr.Expression
22 import com.github.javaparser.ast.expr.FieldAccessExpr
23 import com.github.javaparser.ast.expr.MethodCallExpr
24 import com.github.javaparser.ast.expr.NameExpr
25 
26 /**
27  * Helper class for visiting all ProtoLog calls.
28  * For every valid call in the given {@code CompilationUnit} a {@code ProtoLogCallVisitor} callback
29  * is executed.
30  */
31 class ProtoLogCallProcessorImpl(
32     private val protoLogClassName: String,
33     private val protoLogGroupClassName: String,
34     private val groupMap: Map<String, LogGroup>
35 ) : ProtoLogCallProcessor {
36     private val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
37     private val protoLogGroupSimpleClassName = protoLogGroupClassName.substringAfterLast('.')
38 
39     private fun getLogGroupName(
40         expr: Expression,
41         isClassImported: Boolean,
42         staticImports: Set<String>,
43         fileName: String
44     ): String {
45         val context = ParsingContext(fileName, expr)
46         return when (expr) {
47             is NameExpr -> when {
48                 expr.nameAsString in staticImports -> expr.nameAsString
49                 else ->
50                     throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
51                             context)
52             }
53             is FieldAccessExpr -> when {
54                 expr.scope.toString() == protoLogGroupClassName || isClassImported &&
55                         expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
56                 else ->
57                     throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
58                             context)
59             }
60             else -> throw InvalidProtoLogCallException("Invalid group argument " +
61                     "- must be ProtoLogGroup enum member reference: $expr", context)
62         }
63     }
64 
65     private fun isProtoCall(
66         call: MethodCallExpr,
67         isLogClassImported: Boolean,
68         staticLogImports: Collection<String>
69     ): Boolean {
70         return call.scope.isPresent && call.scope.get().toString() == protoLogClassName ||
71                 isLogClassImported && call.scope.isPresent &&
72                 call.scope.get().toString() == protoLogSimpleClassName ||
73                 !call.scope.isPresent && staticLogImports.contains(call.name.toString())
74     }
75 
76     fun process(code: CompilationUnit, logCallVisitor: ProtoLogCallVisitor?, fileName: String):
77             CompilationUnit {
78         return process(code, logCallVisitor, null, fileName)
79     }
80 
81     override fun process(
82         code: CompilationUnit,
83         logCallVisitor: ProtoLogCallVisitor?,
84         otherCallVisitor: MethodCallVisitor?,
85         fileName: String
86     ): CompilationUnit {
87         CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName)
88         CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName)
89 
90         val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName)
91         val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName)
92         val isGroupClassImported = CodeUtils.isClassImportedOrSamePackage(code,
93                 protoLogGroupClassName)
94         val staticGroupImports = CodeUtils.staticallyImportedMethods(code, protoLogGroupClassName)
95 
96         code.findAll(MethodCallExpr::class.java)
97                 .filter { call ->
98                     isProtoCall(call, isLogClassImported, staticLogImports)
99                 }.forEach { call ->
100                     val context = ParsingContext(fileName, call)
101 
102                     val logMethods = LogLevel.entries.map { it.shortCode }
103                     if (logMethods.contains(call.name.id)) {
104                         // Process a log call
105                         if (call.arguments.size < 2) {
106                             throw InvalidProtoLogCallException("Method signature does not match " +
107                                     "any ProtoLog method: $call", context)
108                         }
109 
110                         val messageString = CodeUtils.concatMultilineString(call.getArgument(1),
111                             context)
112                         val groupNameArg = call.getArgument(0)
113                         val groupName =
114                             getLogGroupName(groupNameArg, isGroupClassImported,
115                                 staticGroupImports, fileName)
116                         if (groupName !in groupMap) {
117                             throw InvalidProtoLogCallException("Unknown group argument " +
118                                     "- not a ProtoLogGroup enum member: $call", context)
119                         }
120 
121                         logCallVisitor?.processCall(call, messageString, getLevelForMethodName(
122                             call.name.toString(), call, context), groupMap.getValue(groupName))
123                     } else {
124                         // Process non-log message calls
125                         otherCallVisitor?.processCall(call)
126                     }
127                 }
128         return code
129     }
130 
131     private fun getLevelForMethodName(
132         name: String,
133         node: MethodCallExpr,
134         context: ParsingContext
135     ): LogLevel = when (name) {
136             "d" -> LogLevel.DEBUG
137             "v" -> LogLevel.VERBOSE
138             "i" -> LogLevel.INFO
139             "w" -> LogLevel.WARN
140             "e" -> LogLevel.ERROR
141             "wtf" -> LogLevel.WTF
142             else ->
143                 throw InvalidProtoLogCallException("Unknown log level $name in $node", context)
144         }
145 }
146