1 package com.android.codegen 2 3 import com.github.javaparser.JavaParser 4 import com.github.javaparser.ParseProblemException 5 import com.github.javaparser.ParseResult 6 import com.github.javaparser.ast.Node 7 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration 8 import com.github.javaparser.ast.body.TypeDeclaration 9 import com.github.javaparser.ast.expr.* 10 import com.github.javaparser.ast.nodeTypes.NodeWithModifiers 11 import java.time.Instant 12 import java.time.ZoneId 13 import java.time.format.DateTimeFormatter 14 import java.time.format.FormatStyle 15 16 /** 17 * [Iterable.forEach] + [Any.apply] 18 */ 19 inline fun <T> Iterable<T>.forEachApply(block: T.() -> Unit) = forEach(block) 20 21 inline fun String.mapLines(f: String.() -> String?) = lines().mapNotNull(f).joinToString("\n") 22 inline fun <T> Iterable<T>.trim(f: T.() -> Boolean) = dropWhile(f).dropLastWhile(f) 23 fun String.trimBlankLines() = lines().trim { isBlank() }.joinToString("\n") 24 25 fun Char.isNewline() = this == '\n' || this == '\r' 26 fun Char.isWhitespaceNonNewline() = isWhitespace() && !isNewline() 27 28 fun if_(cond: Boolean, then: String) = if (cond) then else "" 29 30 fun <T> Any?.as_(): T = this as T 31 32 inline infix fun Int.times(action: () -> Unit) { 33 for (i in 1..this) action() 34 } 35 36 /** 37 * a bbb 38 * cccc dd 39 * 40 * -> 41 * 42 * a bbb 43 * cccc dd 44 */ 45 fun Iterable<Pair<String, String>>.columnize(separator: String = " | "): String { 46 val col1w = map { (a, _) -> a.length }.max()!! 47 val col2w = map { (_, b) -> b.length }.max()!! 48 return map { it.first.padEnd(col1w) + separator + it.second.padEnd(col2w) }.joinToString("\n") 49 } 50 51 fun String.hasUnbalancedCurlyBrace(): Boolean { 52 var braces = 0 53 forEach { 54 if (it == '{') braces++ 55 if (it == '}') braces-- 56 if (braces < 0) return true 57 } 58 return false 59 } 60 61 fun String.toLowerCamel(): String { 62 if (length >= 2 && this[0] == 'm' && this[1].isUpperCase()) return substring(1).capitalize() 63 if (all { it.isLetterOrDigit() }) return decapitalize() 64 return split("[^a-zA-Z0-9]".toRegex()) 65 .map { it.toLowerCase().capitalize() } 66 .joinToString("") 67 .decapitalize() 68 } 69 70 inline fun <T> List<T>.forEachLastAware(f: (T, Boolean) -> Unit) { 71 forEachIndexed { index, t -> f(t, index == size - 1) } 72 } 73 74 @Suppress("UNCHECKED_CAST") 75 fun <T : Expression> AnnotationExpr.singleArgAs() 76 = ((this as SingleMemberAnnotationExpr).memberValue as T) 77 78 inline operator fun <reified T> Array<T>.minus(item: T) = toList().minus(item).toTypedArray() 79 80 fun currentTimestamp() = DateTimeFormatter 81 .ofLocalizedDateTime(/* date */ FormatStyle.MEDIUM, /* time */ FormatStyle.LONG) 82 .withZone(ZoneId.systemDefault()) 83 .format(Instant.now()) 84 85 val NodeWithModifiers<*>.visibility get() = accessSpecifier 86 87 fun abort(msg: String): Nothing { 88 System.err.println("ERROR: $msg") 89 System.exit(1) 90 throw InternalError() // can't get here 91 } 92 93 fun bitAtExpr(bitIndex: Int) = "0x${java.lang.Long.toHexString(1L shl bitIndex)}" 94 95 val AnnotationExpr.args: Map<String, Expression> get() = when (this) { 96 is MarkerAnnotationExpr -> emptyMap() 97 is SingleMemberAnnotationExpr -> mapOf("value" to memberValue) 98 is NormalAnnotationExpr -> pairs.map { it.name.asString() to it.value }.toMap() 99 else -> throw IllegalArgumentException("Unknown annotation expression: $this") 100 } 101 102 val TypeDeclaration<*>.nestedTypes get() = childNodes.filterIsInstance<TypeDeclaration<*>>() 103 val TypeDeclaration<*>.nestedDataClasses get() 104 = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>() 105 .filter { it.annotations.any { it.nameAsString.endsWith("DataClass") } } 106 val TypeDeclaration<*>.nestedNonDataClasses get() 107 = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>() 108 .filter { it.annotations.none { it.nameAsString.endsWith("DataClass") } } 109 .filterNot { it.isInterface } 110 val TypeDeclaration<*>.startLine get() = range.get()!!.begin.line 111 112 inline fun <T> List<T>.forEachSequentialPair(action: (T, T?) -> Unit) { 113 forEachIndexed { index, t -> 114 action(t, getOrNull(index + 1)) 115 } 116 } 117 118 fun <T: Node> parseJava(fn: JavaParser.(String) -> ParseResult<T>, source: String): T = try { 119 val parse = JAVA_PARSER.fn(source) 120 if (parse.problems.isNotEmpty()) { 121 throw parseFailed( 122 source, 123 desc = parse.problems.joinToString("\n"), 124 cause = parse.problems.mapNotNull { it.cause.orElse(null) }.firstOrNull()) 125 } 126 parse.result.get() 127 } catch (e: ParseProblemException) { 128 throw parseFailed(source, cause = e) 129 } 130 131 private fun parseFailed(source: String, cause: Throwable? = null, desc: String = ""): RuntimeException { 132 return RuntimeException("Failed to parse code:\n" + 133 source 134 .lines() 135 .mapIndexed { lnNum, ln -> "/*$lnNum*/$ln" } 136 .joinToString("\n") + "\n$desc", 137 cause) 138 } 139 140 var <T> MutableList<T>.last 141 get() = last() 142 set(value) { 143 if (isEmpty()) { 144 add(value) 145 } else { 146 this[size - 1] = value 147 } 148 } 149 150 inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init)