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.github.javaparser.ast.CompilationUnit
20 import com.github.javaparser.ast.expr.Expression
21 import com.github.javaparser.ast.expr.FieldAccessExpr
22 import com.github.javaparser.ast.expr.MethodCallExpr
23 import com.github.javaparser.ast.expr.NameExpr
24 
25 /**
26  * Helper class for visiting all ProtoLog calls.
27  * For every valid call in the given {@code CompilationUnit} a {@code ProtoLogCallVisitor} callback
28  * is executed.
29  */
30 open class ProtoLogCallProcessor(
31     private val protoLogClassName: String,
32     private val protoLogGroupClassName: String,
33     private val groupMap: Map<String, LogGroup>
34 ) {
35     private val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
36     private val protoLogGroupSimpleClassName = protoLogGroupClassName.substringAfterLast('.')
37 
38     private fun getLogGroupName(
39         expr: Expression,
40         isClassImported: Boolean,
41         staticImports: Set<String>,
42         fileName: String
43     ): String {
44         val context = ParsingContext(fileName, expr)
45         return when (expr) {
46             is NameExpr -> when {
47                 expr.nameAsString in staticImports -> expr.nameAsString
48                 else ->
49                     throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
50                             context)
51             }
52             is FieldAccessExpr -> when {
53                 expr.scope.toString() == protoLogGroupClassName
54                         || 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     open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?, fileName: String):
77             CompilationUnit {
78         CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName)
79         CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName)
80 
81         val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName)
82         val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName)
83         val isGroupClassImported = CodeUtils.isClassImportedOrSamePackage(code,
84                 protoLogGroupClassName)
85         val staticGroupImports = CodeUtils.staticallyImportedMethods(code, protoLogGroupClassName)
86 
87         code.findAll(MethodCallExpr::class.java)
88                 .filter { call ->
89                     isProtoCall(call, isLogClassImported, staticLogImports)
90                 }.forEach { call ->
91                     val context = ParsingContext(fileName, call)
92                     if (call.arguments.size < 2) {
93                         throw InvalidProtoLogCallException("Method signature does not match " +
94                                 "any ProtoLog method: $call", context)
95                     }
96 
97                     val messageString = CodeUtils.concatMultilineString(call.getArgument(1),
98                             context)
99                     val groupNameArg = call.getArgument(0)
100                     val groupName =
101                             getLogGroupName(groupNameArg, isGroupClassImported,
102                                     staticGroupImports, fileName)
103                     if (groupName !in groupMap) {
104                         throw InvalidProtoLogCallException("Unknown group argument " +
105                                 "- not a ProtoLogGroup enum member: $call", context)
106                     }
107 
108                     callVisitor?.processCall(call, messageString, LogLevel.getLevelForMethodName(
109                             call.name.toString(), call, context), groupMap.getValue(groupName))
110                 }
111         return code
112     }
113 }
114