| package com.android.codegen |
| |
| import com.github.javaparser.JavaParser |
| import com.github.javaparser.ParseProblemException |
| import com.github.javaparser.ParseResult |
| import com.github.javaparser.ast.Node |
| import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration |
| import com.github.javaparser.ast.body.TypeDeclaration |
| import com.github.javaparser.ast.expr.* |
| import com.github.javaparser.ast.nodeTypes.NodeWithModifiers |
| import java.time.Instant |
| import java.time.ZoneId |
| import java.time.format.DateTimeFormatter |
| import java.time.format.FormatStyle |
| |
| /** |
| * [Iterable.forEach] + [Any.apply] |
| */ |
| inline fun <T> Iterable<T>.forEachApply(block: T.() -> Unit) = forEach(block) |
| |
| inline fun String.mapLines(f: String.() -> String?) = lines().mapNotNull(f).joinToString("\n") |
| inline fun <T> Iterable<T>.trim(f: T.() -> Boolean) = dropWhile(f).dropLastWhile(f) |
| fun String.trimBlankLines() = lines().trim { isBlank() }.joinToString("\n") |
| |
| fun Char.isNewline() = this == '\n' || this == '\r' |
| fun Char.isWhitespaceNonNewline() = isWhitespace() && !isNewline() |
| |
| fun if_(cond: Boolean, then: String) = if (cond) then else "" |
| |
| fun <T> Any?.as_(): T = this as T |
| |
| inline infix fun Int.times(action: () -> Unit) { |
| for (i in 1..this) action() |
| } |
| |
| /** |
| * a bbb |
| * cccc dd |
| * |
| * -> |
| * |
| * a bbb |
| * cccc dd |
| */ |
| fun Iterable<Pair<String, String>>.columnize(separator: String = " | "): String { |
| val col1w = map { (a, _) -> a.length }.maxOrNull()!! |
| val col2w = map { (_, b) -> b.length }.maxOrNull()!! |
| return map { it.first.padEnd(col1w) + separator + it.second.padEnd(col2w) }.joinToString("\n") |
| } |
| |
| fun String.hasUnbalancedCurlyBrace(): Boolean { |
| var braces = 0 |
| forEach { |
| if (it == '{') braces++ |
| if (it == '}') braces-- |
| if (braces < 0) return true |
| } |
| return false |
| } |
| |
| fun String.toLowerCamel(): String { |
| if (length >= 2 && this[0] == 'm' && this[1].isUpperCase()) return substring(1).capitalize() |
| if (all { it.isLetterOrDigit() }) return decapitalize() |
| return split("[^a-zA-Z0-9]".toRegex()) |
| .map { it.toLowerCase().capitalize() } |
| .joinToString("") |
| .decapitalize() |
| } |
| |
| inline fun <T> List<T>.forEachLastAware(f: (T, Boolean) -> Unit) { |
| forEachIndexed { index, t -> f(t, index == size - 1) } |
| } |
| |
| @Suppress("UNCHECKED_CAST") |
| fun <T : Expression> AnnotationExpr.singleArgAs() |
| = ((this as SingleMemberAnnotationExpr).memberValue as T) |
| |
| inline operator fun <reified T> Array<T>.minus(item: T) = toList().minus(item).toTypedArray() |
| |
| fun currentTimestamp() = DateTimeFormatter |
| .ofLocalizedDateTime(/* date */ FormatStyle.MEDIUM, /* time */ FormatStyle.LONG) |
| .withZone(ZoneId.systemDefault()) |
| .format(Instant.now()) |
| |
| val NodeWithModifiers<*>.visibility get() = accessSpecifier |
| |
| fun abort(msg: String): Nothing { |
| System.err.println("ERROR: $msg") |
| System.exit(1) |
| throw InternalError() // can't get here |
| } |
| |
| fun bitAtExpr(bitIndex: Int) = "0x${java.lang.Long.toHexString(1L shl bitIndex)}" |
| |
| val AnnotationExpr.args: Map<String, Expression> get() = when (this) { |
| is MarkerAnnotationExpr -> emptyMap() |
| is SingleMemberAnnotationExpr -> mapOf("value" to memberValue) |
| is NormalAnnotationExpr -> pairs.map { it.name.asString() to it.value }.toMap() |
| else -> throw IllegalArgumentException("Unknown annotation expression: $this") |
| } |
| |
| val TypeDeclaration<*>.nestedTypes get() = childNodes.filterIsInstance<TypeDeclaration<*>>() |
| val TypeDeclaration<*>.nestedDataClasses get() |
| = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>() |
| .filter { it.annotations.any { it.nameAsString.endsWith("DataClass") } } |
| val TypeDeclaration<*>.nestedNonDataClasses get() |
| = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>() |
| .filter { it.annotations.none { it.nameAsString.endsWith("DataClass") } } |
| .filterNot { it.isInterface } |
| val TypeDeclaration<*>.startLine get() = range.get()!!.begin.line |
| |
| inline fun <T> List<T>.forEachSequentialPair(action: (T, T?) -> Unit) { |
| forEachIndexed { index, t -> |
| action(t, getOrNull(index + 1)) |
| } |
| } |
| |
| fun <T: Node> parseJava(fn: JavaParser.(String) -> ParseResult<T>, source: String): T = try { |
| val parse = JAVA_PARSER.fn(source) |
| if (parse.problems.isNotEmpty()) { |
| throw parseFailed( |
| source, |
| desc = parse.problems.joinToString("\n"), |
| cause = parse.problems.mapNotNull { it.cause.orElse(null) }.firstOrNull()) |
| } |
| parse.result.get() |
| } catch (e: ParseProblemException) { |
| throw parseFailed(source, cause = e) |
| } |
| |
| private fun parseFailed(source: String, cause: Throwable? = null, desc: String = ""): RuntimeException { |
| return RuntimeException("Failed to parse code:\n" + |
| source |
| .lines() |
| .mapIndexed { lnNum, ln -> "/*$lnNum*/$ln" } |
| .joinToString("\n") + "\n$desc", |
| cause) |
| } |
| |
| var <T> MutableList<T>.last |
| get() = last() |
| set(value) { |
| if (isEmpty()) { |
| add(value) |
| } else { |
| this[size - 1] = value |
| } |
| } |
| |
| inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init) |