blob: 3c55237ce4436c042c30b239402730a9c77e1901 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.protolog.tool
import com.android.protolog.tool.CommandOptions.Companion.USAGE
import com.github.javaparser.ParseProblemException
import com.github.javaparser.ParserConfiguration
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.OutputStream
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.system.exitProcess
object ProtoLogTool {
private fun showHelpAndExit() {
println(USAGE)
exitProcess(-1)
}
private fun containsProtoLogText(source: String, protoLogClassName: String): Boolean {
val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
return source.contains(protoLogSimpleClassName)
}
private fun processClasses(command: CommandOptions) {
val groups = injector.readLogGroups(
command.protoLogGroupsJarArg,
command.protoLogGroupsClassNameArg)
val out = injector.fileOutputStream(command.outputSourceJarArg)
val outJar = JarOutputStream(out)
val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
command.protoLogGroupsClassNameArg, groups)
val executor = newThreadPool()
try {
command.javaSourceArgs.map { path ->
executor.submitCallable {
val transformer = SourceTransformer(command.protoLogImplClassNameArg,
command.protoLogCacheClassNameArg, processor)
val file = File(path)
val text = injector.readText(file)
val outSrc = try {
val code = tryParse(text, path)
if (containsProtoLogText(text, command.protoLogClassNameArg)) {
transformer.processClass(text, path, packagePath(file, code), code)
} else {
text
}
} catch (ex: ParsingException) {
// If we cannot parse this file, skip it (and log why). Compilation will
// fail in a subsequent build step.
injector.reportParseError(ex)
text
}
path to outSrc
}
}.map { future ->
val (path, outSrc) = future.get()
outJar.putNextEntry(ZipEntry(path))
outJar.write(outSrc.toByteArray())
outJar.closeEntry()
}
} finally {
executor.shutdown()
}
val cacheSplit = command.protoLogCacheClassNameArg.split(".")
val cacheName = cacheSplit.last()
val cachePackage = cacheSplit.dropLast(1).joinToString(".")
val cachePath = "gen/${cacheSplit.joinToString("/")}.java"
outJar.putNextEntry(ZipEntry(cachePath))
outJar.write(generateLogGroupCache(cachePackage, cacheName, groups,
command.protoLogImplClassNameArg, command.protoLogGroupsClassNameArg).toByteArray())
outJar.close()
out.close()
}
fun generateLogGroupCache(
cachePackage: String,
cacheName: String,
groups: Map<String, LogGroup>,
protoLogImplClassName: String,
protoLogGroupsClassName: String
): String {
val fields = groups.values.map {
"public static boolean ${it.name}_enabled = false;"
}.joinToString("\n")
val updates = groups.values.map {
"${it.name}_enabled = " +
"$protoLogImplClassName.isEnabled($protoLogGroupsClassName.${it.name});"
}.joinToString("\n")
return """
package $cachePackage;
public class $cacheName {
${fields.replaceIndent(" ")}
static {
$protoLogImplClassName.sCacheUpdater = $cacheName::update;
update();
}
static void update() {
${updates.replaceIndent(" ")}
}
}
""".trimIndent()
}
private fun tryParse(code: String, fileName: String): CompilationUnit {
try {
return StaticJavaParser.parse(code)
} catch (ex: ParseProblemException) {
val problem = ex.problems.first()
throw ParsingException("Java parsing erro" +
"r: ${problem.verboseMessage}",
ParsingContext(fileName, problem.location.orElse(null)
?.begin?.range?.orElse(null)?.begin?.line
?: 0))
}
}
private fun viewerConf(command: CommandOptions) {
val groups = injector.readLogGroups(
command.protoLogGroupsJarArg,
command.protoLogGroupsClassNameArg)
val processor = ProtoLogCallProcessor(command.protoLogClassNameArg,
command.protoLogGroupsClassNameArg, groups)
val builder = ViewerConfigBuilder(processor)
val executor = newThreadPool()
try {
command.javaSourceArgs.map { path ->
executor.submitCallable {
val file = File(path)
val text = injector.readText(file)
if (containsProtoLogText(text, command.protoLogClassNameArg)) {
try {
val code = tryParse(text, path)
builder.findLogCalls(code, path, packagePath(file, code))
} catch (ex: ParsingException) {
// If we cannot parse this file, skip it (and log why). Compilation will
// fail in a subsequent build step.
injector.reportParseError(ex)
null
}
} else {
null
}
}
}.forEach { future ->
builder.addLogCalls(future.get() ?: return@forEach)
}
} finally {
executor.shutdown()
}
val out = injector.fileOutputStream(command.viewerConfigJsonArg)
out.write(builder.build().toByteArray())
out.close()
}
private fun packagePath(file: File, code: CompilationUnit): String {
val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
.get().nameAsString else ""
val packagePath = pack.replace('.', '/') + '/' + file.name
return packagePath
}
private fun read(command: CommandOptions) {
LogParser(ViewerConfigParser())
.parse(FileInputStream(command.logProtofileArg),
FileInputStream(command.viewerConfigJsonArg), System.out)
}
@JvmStatic
fun main(args: Array<String>) {
try {
val command = CommandOptions(args)
invoke(command)
} catch (ex: InvalidCommandException) {
println("\n${ex.message}\n")
showHelpAndExit()
} catch (ex: CodeProcessingException) {
println("\n${ex.message}\n")
exitProcess(1)
}
}
fun invoke(command: CommandOptions) {
StaticJavaParser.setConfiguration(ParserConfiguration().apply {
setLanguageLevel(ParserConfiguration.LanguageLevel.RAW)
setAttributeComments(false)
})
when (command.command) {
CommandOptions.TRANSFORM_CALLS_CMD -> processClasses(command)
CommandOptions.GENERATE_CONFIG_CMD -> viewerConf(command)
CommandOptions.READ_LOG_CMD -> read(command)
}
}
var injector = object : Injector {
override fun fileOutputStream(file: String) = FileOutputStream(file)
override fun readText(file: File) = file.readText()
override fun readLogGroups(jarPath: String, className: String) =
ProtoLogGroupReader().loadFromJar(jarPath, className)
override fun reportParseError(ex: ParsingException) {
println("\n${ex.message}\n")
}
}
interface Injector {
fun fileOutputStream(file: String): OutputStream
fun readText(file: File): String
fun readLogGroups(jarPath: String, className: String): Map<String, LogGroup>
fun reportParseError(ex: ParsingException)
}
}
private fun <T> ExecutorService.submitCallable(f: () -> T) = submit(f)
private fun newThreadPool() = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors())