blob: 7cfa7847fcffda8f5f50141b2a85d56ab5377f93 [file] [log] [blame]
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 }.max()!!
val col2w = map { (_, b) -> b.length }.max()!!
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)