1
0

Inject challenges input

This commit is contained in:
Hubert Van De Walle 2020-12-02 01:52:20 +01:00
parent b470715f12
commit 7e31325d16
16 changed files with 314 additions and 44 deletions

View File

@ -19,9 +19,24 @@ object Libs {
const val processor = "org.openjdk.jmh:jmh-generator-annprocess:1.26"
}
object Micronaut {
private const val version = "2.2.0"
const val inject = "io.micronaut:micronaut-inject:$version"
const val core = "io.micronaut:micronaut-core:$version"
const val kotlin = "io.micronaut.kotlin:micronaut-kotlin-extension-functions:$version"
const val processor = "io.micronaut:micronaut-inject-java:$version"
}
object Slf4J {
const val api = "org.slf4j:slf4j-api:1.7.25"
const val logback = "ch.qos.logback:logback-classic:1.2.3"
const val kotlin = "io.github.microutils:kotlin-logging-jvm:2.0.3"
}
object Tests {
const val assertJ = "org.assertj:assertj-core:3.18.1"
const val junit = "org.junit.jupiter:junit-jupiter:5.7.0"
const val jimfs = "com.google.jimfs:jimfs:1.1"
}
}

View File

@ -1,7 +1,13 @@
plugins {
id("kotlin-convention")
kotlin("kapt")
}
dependencies {
implementation(project(":utils"))
implementation(Libs.Micronaut.core)
implementation(Libs.Micronaut.inject)
implementation(Libs.Micronaut.kotlin)
kapt(Libs.Micronaut.processor)
}

View File

@ -1,22 +1,30 @@
@file:Suppress("unused")
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.Resources
import be.vandewalleh.aoc.utils.input.Day
import be.vandewalleh.aoc.utils.input.Input
import be.vandewalleh.aoc.utils.input.Lines
import io.micronaut.context.BeanContext
import io.micronaut.context.getBean
object Day01 {
@Day(1)
class Day01(@Lines input: Input<IntArray>) {
private val items = input.value
fun part1(list: List<Int>): Int? {
list.forEach { a ->
list.forEach { b ->
fun part1(): Int? {
items.forEach { a ->
items.forEach { b ->
if (a + b == 2020) return a * b
}
}
return null
}
fun part2(list: List<Int>): Int? {
list.forEach { a ->
list.forEach { b ->
list.forEach { c ->
fun part2(): Int? {
items.forEach { a ->
items.forEach { b ->
items.forEach { c ->
if (a + b + c == 2020) return a * b * c
}
}
@ -27,7 +35,7 @@ object Day01 {
}
fun main() {
val input = Resources.ints("day01.txt").toList()
println(Day01.part1(input))
println(Day01.part2(input))
val day = BeanContext.run().getBean<Day01>()
println(day.part1())
println(day.part2())
}

View File

@ -1,21 +1,22 @@
package be.vandewalleh.aoc.days
import be.vandewalleh.aoc.utils.Resources
import io.micronaut.context.BeanContext
import io.micronaut.context.getBean
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class Day01Test {
private val input = Resources.ints("day01.txt").toList()
private val day = BeanContext.run().getBean<Day01>()
@Test
fun `part1 result`() {
assertThat(Day01.part1(input)).isEqualTo(32064)
assertThat(day.part1()).isEqualTo(32064)
}
@Test
fun `part2 result`() {
assertThat(Day01.part2(input)).isEqualTo(193598720)
assertThat(day.part2()).isEqualTo(193598720)
}
}

View File

@ -1,3 +1,18 @@
plugins {
id("kotlin-convention")
kotlin("kapt")
}
dependencies {
implementation(Libs.Micronaut.core)
implementation(Libs.Micronaut.inject)
implementation(Libs.Micronaut.kotlin)
kapt(Libs.Micronaut.processor)
implementation(Libs.Slf4J.api)
implementation(Libs.Slf4J.kotlin)
runtimeOnly(Libs.Slf4J.logback)
testImplementation(Libs.Tests.jimfs)
kaptTest(Libs.Micronaut.processor)
}

View File

@ -0,0 +1 @@
package be.vandewalleh.aoc.utils

View File

@ -1,28 +0,0 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package be.vandewalleh.aoc.utils
import java.nio.file.Files
import java.nio.file.Path
import kotlin.streams.asSequence
object Resources {
private fun path(name: String): Path {
val url = if (name.startsWith('/')) javaClass.getResource(name)
else javaClass.getResource("/$name")
return Path.of(url.toURI())
}
private fun Path.lines() = Files.lines(this)
.asSequence()
.filter { it.isNotBlank() }
.map { it.trim() }
private fun Path.text() = Files.readString(this).trim()
fun text(name: String) = path(name).text()
fun lines(name: String) = path(name).lines()
fun ints(name: String) = lines(name).map { it.toInt() }
fun csv(name: String) = path(name).text().splitToSequence(",")
fun csvLong(name: String) = csv(name).map { it.toLong() }
}

View File

@ -0,0 +1,26 @@
@file:Suppress("unused")
package be.vandewalleh.aoc.utils.input
import io.micronaut.context.annotation.Prototype
import io.micronaut.core.annotation.Introspected
import javax.inject.Qualifier
@Prototype
@Qualifier
@Introspected
@Target(AnnotationTarget.CLASS)
annotation class Day(val day: Int)
@Qualifier
@Prototype
annotation class DayInput
@DayInput
annotation class Csv
@DayInput
annotation class Text
@DayInput
annotation class Lines

View File

@ -0,0 +1,7 @@
package be.vandewalleh.aoc.utils.input
/**
* Wrapper class so that micronaut is not confused with an other injectable
* container type when using an iterable / array
*/
data class Input<T>(val value: T)

View File

@ -0,0 +1,76 @@
package be.vandewalleh.aoc.utils.input
import io.micronaut.context.annotation.Factory
import io.micronaut.inject.InjectionPoint
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.readLines
import kotlin.io.path.readText
/**
* Load challenge inputs and convert them to the appropriate format
*
* contains horrible hacks while waiting for generic support in bean injection
* @see [micronaut-2775](https://github.com/micronaut-projects/micronaut-core/issues/2775)
*/
@Factory
@ExperimentalPathApi
class InputFactory(private val resourceLoader: ResourceLoader) {
@Csv
fun csvIntArray(injectionPoint: InjectionPoint<*>): Input<IntArray> = injectionPoint
.read()
.split(",")
.map { it.toInt() }
.toIntArray()
.wrap()
@Lines
fun linesIntArray(injectionPoint: InjectionPoint<*>): Input<*> =
when (val param = injectionPoint.typeNameOfAnnotation<Lines>()) {
"int[]" -> injectionPoint
.lines()
.map { it.toInt() }
.toList()
.toIntArray()
.wrap()
"java.util.List<java.lang.String>" -> injectionPoint
.lines()
.toList()
.wrap()
else -> error("Unsupported type $param")
}
@Text
fun string(injectionPoint: InjectionPoint<*>): Input<String> =
injectionPoint.read().wrap()
private fun <T> T.wrap() = Input(this)
private inline fun <reified T : Annotation> InjectionPoint<*>.typeNameOfAnnotation() = declaringBean
.constructor
.arguments
.find { it.hasAnnotation<T>() }
?.typeName
?.removePrefix("be.vandewalleh.aoc.utils.input.Input<")
?.removeSuffix(">")
?: error("??")
private fun InjectionPoint<*>.path() = resourceLoader.ofDay(getDay(this))
private fun InjectionPoint<*>.read() = path().readText().trim()
private fun InjectionPoint<*>.lines() = path().readLines()
.asSequence()
.filter { it.isNotBlank() }
private fun getDay(injectionPoint: InjectionPoint<*>): Int {
val dayAnnotation = injectionPoint
.declaringBean
.findAnnotation<Day>()
if (dayAnnotation.isEmpty)
error("@DayInput cannot only be used on classes annotated with ${Day::class.qualifiedName}")
return dayAnnotation.get().intValue("day").asInt
}
}

View File

@ -0,0 +1,19 @@
package be.vandewalleh.aoc.utils.input
import io.micronaut.core.annotation.AnnotationMetadataDelegate
import io.micronaut.core.annotation.AnnotationMetadataProvider
import io.micronaut.core.annotation.AnnotationValue
import io.micronaut.core.beans.BeanIntrospection
import java.util.*
internal inline fun <reified T> getIntrospection(): BeanIntrospection<T> =
BeanIntrospection.getIntrospection(T::class.java)
internal inline fun <reified T : Annotation> AnnotationMetadataDelegate.getAnnotation(): AnnotationValue<T>? =
getAnnotation(T::class.java)
internal inline fun <reified T : Annotation> AnnotationMetadataProvider.findAnnotation(): Optional<AnnotationValue<T>> =
findAnnotation(T::class.java)
internal inline fun <reified T : Annotation> AnnotationMetadataProvider.hasAnnotation(): Boolean =
findAnnotation(T::class.java).isPresent

View File

@ -0,0 +1,21 @@
package be.vandewalleh.aoc.utils.input
import java.nio.file.Path
import javax.inject.Singleton
interface ResourceLoader {
fun ofDay(day: Int): Path
}
@Singleton
class ResourceLoaderImpl : ResourceLoader {
override fun ofDay(day: Int): Path {
val resourcePath = buildString {
append("/day")
append(day.toString().padStart(2, '0'))
append(".txt")
}
val url = javaClass.getResource(resourcePath)
return Path.of(url.toURI())
}
}

View File

@ -0,0 +1,14 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<withJansi>true</withJansi>
<encoder>
<pattern>%cyan(%d{YYYY-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %green(%logger{36}) - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
<logger name="io.micronaut" level="INFO"/>
<logger name="io.micronaut.context.lifecycle" level="INFO"/>
</configuration>

View File

@ -0,0 +1 @@
package be.vandewalleh.aoc.utils

View File

@ -0,0 +1,84 @@
package be.vandewalleh.aoc.utils.input
import com.google.common.jimfs.Jimfs
import io.micronaut.context.BeanContext
import io.micronaut.context.annotation.Replaces
import io.micronaut.context.getBean
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import javax.inject.Singleton
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.writeText
class DayTest {
@Singleton
@Replaces(ResourceLoaderImpl::class)
@ExperimentalPathApi
class FakeResourceLoader : ResourceLoader {
private val inMemoryFs = Jimfs.newFileSystem().apply {
getPath("1").writeText("blablabla")
getPath("2").writeText("1,+2,3,4,-5")
getPath("3")
.writeText(
"""
1779
1737
1537
1167
1804
1873
""".trimIndent()
)
getPath("4")
.also { println(it) }
.writeText(
"""
a
bb
ccc
""".trimIndent()
)
}
override fun ofDay(day: Int) = inMemoryFs.getPath(day.toString())
}
@Day(1)
class TextStringExample(@Text val input: Input<String>)
@Day(2)
class CsvIntArrayExample(@Csv val input: Input<IntArray>)
@Day(3)
class IntLinesExample(@Lines val input: Input<IntArray>)
@Day(4)
class StringLinesExample(@Lines val input: Input<List<String>>)
private inline fun <reified T> createDay() = BeanContext.run().getBean<T>()
@Test
fun `check @Text String`() {
val day = createDay<TextStringExample>()
assertThat(day.input.value).isEqualTo("blablabla")
}
@Test
fun `check @Csv IntArray`() {
val day = createDay<CsvIntArrayExample>()
assertThat(day.input.value).containsExactly(1, +2, 3, 4, -5)
}
@Test
fun `check @Lines IntArray`() {
val day = createDay<IntLinesExample>()
assertThat(day.input.value).containsExactly(1779, 1737, 1537, 1167, 1804, 1873)
}
@Test
fun `check @Lines strings`() {
val day = createDay<StringLinesExample>()
assertThat(day.input.value).containsExactly("a", "bb", "ccc")
}
}

View File

@ -0,0 +1,4 @@
junit.jupiter.testinstance.lifecycle.default=per_class
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=same_thread
junit.jupiter.execution.parallel.mode.classes.default=concurrent