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.protolog.tool.CommandOptions.Companion.USAGE
20 import com.github.javaparser.ParseProblemException
21 import com.github.javaparser.ParserConfiguration
22 import com.github.javaparser.StaticJavaParser
23 import com.github.javaparser.ast.CompilationUnit
24 import java.io.File
25 import java.io.FileInputStream
26 import java.io.FileOutputStream
27 import java.io.OutputStream
28 import java.util.concurrent.ExecutorService
29 import java.util.concurrent.Executors
30 import java.util.jar.JarOutputStream
31 import java.util.zip.ZipEntry
32 import kotlin.system.exitProcess
33 
34 object ProtoLogTool {
35     private fun showHelpAndExit() {
36         println(USAGE)
37         exitProcess(-1)
38     }
39 
40     private fun containsProtoLogText(source: String, protoLogClassName: String): Boolean {
41         val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
42         return source.contains(protoLogSimpleClassName)
43     }
44 
45     private fun processClasses(command: CommandOptions) {
46         val groups = injector.readLogGroups(
47                 command.protoLogGroupsJarArg,
48                 command.protoLogGroupsClassNameArg)
49         val out = injector.fileOutputStream(command.outputSourceJarArg)
50         val outJar = JarOutputStream(out)
51         val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
52                 command.protoLogGroupsClassNameArg, groups)
53 
54         val executor = newThreadPool()
55 
56         try {
57             command.javaSourceArgs.map { path ->
58                 executor.submitCallable {
59                     val transformer = SourceTransformer(command.protoLogImplClassNameArg,
60                             command.protoLogCacheClassNameArg, processor)
61                     val file = File(path)
62                     val text = injector.readText(file)
63                     val outSrc = try {
64                         val code = tryParse(text, path)
65                         if (containsProtoLogText(text, command.protoLogClassNameArg)) {
66                             transformer.processClass(text, path, packagePath(file, code), code)
67                         } else {
68                             text
69                         }
70                     } catch (ex: ParsingException) {
71                         // If we cannot parse this file, skip it (and log why). Compilation will
72                         // fail in a subsequent build step.
73                         injector.reportParseError(ex)
74                         text
75                     }
76                     path to outSrc
77                 }
78             }.map { future ->
79                 val (path, outSrc) = future.get()
80                 outJar.putNextEntry(ZipEntry(path))
81                 outJar.write(outSrc.toByteArray())
82                 outJar.closeEntry()
83             }
84         } finally {
85             executor.shutdown()
86         }
87 
88         val cacheSplit = command.protoLogCacheClassNameArg.split(".")
89         val cacheName = cacheSplit.last()
90         val cachePackage = cacheSplit.dropLast(1).joinToString(".")
91         val cachePath = "gen/${cacheSplit.joinToString("/")}.java"
92 
93         outJar.putNextEntry(ZipEntry(cachePath))
94         outJar.write(generateLogGroupCache(cachePackage, cacheName, groups,
95                 command.protoLogImplClassNameArg, command.protoLogGroupsClassNameArg).toByteArray())
96 
97         outJar.close()
98         out.close()
99     }
100 
101     fun generateLogGroupCache(
102         cachePackage: String,
103         cacheName: String,
104         groups: Map<String, LogGroup>,
105         protoLogImplClassName: String,
106         protoLogGroupsClassName: String
107     ): String {
108         val fields = groups.values.map {
109             "public static boolean ${it.name}_enabled = false;"
110         }.joinToString("\n")
111 
112         val updates = groups.values.map {
113             "${it.name}_enabled = " +
114                     "$protoLogImplClassName.isEnabled($protoLogGroupsClassName.${it.name});"
115         }.joinToString("\n")
116 
117         return """
118             package $cachePackage;
119 
120             public class $cacheName {
121 ${fields.replaceIndent("                ")}
122 
123                 static {
124                     $protoLogImplClassName.sCacheUpdater = $cacheName::update;
125                     update();
126                 }
127 
128                 static void update() {
129 ${updates.replaceIndent("                    ")}
130                 }
131             }
132         """.trimIndent()
133     }
134 
135     private fun tryParse(code: String, fileName: String): CompilationUnit {
136         try {
137             return StaticJavaParser.parse(code)
138         } catch (ex: ParseProblemException) {
139             val problem = ex.problems.first()
140             throw ParsingException("Java parsing erro" +
141                     "r: ${problem.verboseMessage}",
142                     ParsingContext(fileName, problem.location.orElse(null)
143                             ?.begin?.range?.orElse(null)?.begin?.line
144                             ?: 0))
145         }
146     }
147 
148     private fun viewerConf(command: CommandOptions) {
149         val groups = injector.readLogGroups(
150                 command.protoLogGroupsJarArg,
151                 command.protoLogGroupsClassNameArg)
152         val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
153                 command.protoLogGroupsClassNameArg, groups)
154         val builder = ViewerConfigBuilder(processor)
155 
156         val executor = newThreadPool()
157 
158         try {
159             command.javaSourceArgs.map { path ->
160                 executor.submitCallable {
161                     val file = File(path)
162                     val text = injector.readText(file)
163                     if (containsProtoLogText(text, command.protoLogClassNameArg)) {
164                         try {
165                             val code = tryParse(text, path)
166                             builder.findLogCalls(code, path, packagePath(file, code))
167                         } catch (ex: ParsingException) {
168                             // If we cannot parse this file, skip it (and log why). Compilation will
169                             // fail in a subsequent build step.
170                             injector.reportParseError(ex)
171                             null
172                         }
173                     } else {
174                         null
175                     }
176                 }
177             }.forEach { future ->
178                 builder.addLogCalls(future.get() ?: return@forEach)
179             }
180         } finally {
181             executor.shutdown()
182         }
183 
184         val out = injector.fileOutputStream(command.viewerConfigJsonArg)
185         out.write(builder.build().toByteArray())
186         out.close()
187     }
188 
189     private fun packagePath(file: File, code: CompilationUnit): String {
190         val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
191                 .get().nameAsString else ""
192         val packagePath = pack.replace('.', '/') + '/' + file.name
193         return packagePath
194     }
195 
196     private fun read(command: CommandOptions) {
197         LogParser(ViewerConfigParser())
198                 .parse(FileInputStream(command.logProtofileArg),
199                         FileInputStream(command.viewerConfigJsonArg), System.out)
200     }
201 
202     @JvmStatic
203     fun main(args: Array<String>) {
204         try {
205             val command = CommandOptions(args)
206             invoke(command)
207         } catch (ex: InvalidCommandException) {
208             println("\n${ex.message}\n")
209             showHelpAndExit()
210         } catch (ex: CodeProcessingException) {
211             println("\n${ex.message}\n")
212             exitProcess(1)
213         }
214     }
215 
216     fun invoke(command: CommandOptions) {
217         StaticJavaParser.setConfiguration(ParserConfiguration().apply {
218             setLanguageLevel(ParserConfiguration.LanguageLevel.RAW)
219             setAttributeComments(false)
220         })
221 
222         when (command.command) {
223             CommandOptions.TRANSFORM_CALLS_CMD -> processClasses(command)
224             CommandOptions.GENERATE_CONFIG_CMD -> viewerConf(command)
225             CommandOptions.READ_LOG_CMD -> read(command)
226         }
227     }
228 
229     var injector = object : Injector {
230         override fun fileOutputStream(file: String) = FileOutputStream(file)
231         override fun readText(file: File) = file.readText()
232         override fun readLogGroups(jarPath: String, className: String) =
233                 ProtoLogGroupReader().loadFromJar(jarPath, className)
234         override fun reportParseError(ex: ParsingException) {
235             println("\n${ex.message}\n")
236         }
237     }
238 
239     interface Injector {
240         fun fileOutputStream(file: String): OutputStream
241         fun readText(file: File): String
242         fun readLogGroups(jarPath: String, className: String): Map<String, LogGroup>
243         fun reportParseError(ex: ParsingException)
244     }
245 }
246 
submitCallablenull247 private fun <T> ExecutorService.submitCallable(f: () -> T) = submit(f)
248 
249 private fun newThreadPool() = Executors.newFixedThreadPool(
250         Runtime.getRuntime().availableProcessors())
251