Skip to content

Commit

Permalink
Renamed DataClassMapper to ObjectMapper and added support for constru…
Browse files Browse the repository at this point in the history
…ctor parameters without a backing field
  • Loading branch information
stefankoppier committed Jun 17, 2024
1 parent 14a8d79 commit b97874a
Show file tree
Hide file tree
Showing 19 changed files with 115 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,22 @@ abstract class CollectionMapper<FROM, TO> : Mapper<List<FROM>, List<TO>>() {
abstract infix fun filteredBy(predicate: (FROM) -> Boolean): CollectionMapper<FROM, TO>
}

// TODO: choose between DataClassMapper and ClassMapper vs just ObjectMapper
abstract class DataClassMapper<FROM, TO> : Mapper<FROM, TO>() {
abstract class ObjectMapper<FROM, TO> : Mapper<FROM, TO>() {

val forList: CollectionMapper<FROM, TO> get() =
error("The mapper forList should only be used in the context of 'via'. Use mapList instead.")

protected infix fun <TO_TYPE, FROM_TYPE> KProperty1<TO, TO_TYPE>.mappedFromProperty(source: KProperty1<FROM, FROM_TYPE>): TransformableValue<FROM_TYPE, TO_TYPE> =
generated()

protected infix fun <TO_TYPE> KProperty1<TO, TO_TYPE>.mappedFromConstant(value: TO_TYPE): DataClassMapper<FROM, TO_TYPE> =
protected infix fun <TO_TYPE> KProperty1<TO, TO_TYPE>.mappedFromConstant(value: TO_TYPE): ObjectMapper<FROM, TO_TYPE> =
generated()

protected infix fun <TO_TYPE> KProperty1<TO, TO_TYPE>.mappedFromExpression(function: (FROM) -> TO_TYPE): DataClassMapper<FROM, TO_TYPE> =
protected infix fun <TO_TYPE> KProperty1<TO, TO_TYPE>.mappedFromExpression(function: (FROM) -> TO_TYPE): ObjectMapper<FROM, TO_TYPE> =
generated()

fun expression(function: (FROM) -> TO): Mapper<FROM, TO> =
protected fun parameter(name: String): KProperty1<TO, *> =
generated()

protected fun result(source: TO): DataClassMapper<FROM, TO> = generated()
protected fun result(source: TO): ObjectMapper<FROM, TO> = generated()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ val IDENTIFIER_MAPPED_FROM_EXPRESSION = Name.identifier("mappedFromExpression")

val IDENTIFIER_RESULT = Name.identifier("result")

val IDENTIFIER_PARAMETER = Name.identifier("parameter")

val IDENTIFIER_TRANFORM = Name.identifier("transform")

val IDENTIFIER_VIA = Name.identifier("via")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.isPrimitiveType
import org.jetbrains.kotlin.ir.types.isString
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name

sealed interface Mapping

Expand All @@ -23,6 +24,7 @@ data class ConstructorCallMapping(
val sourceType: IrType,
val symbol: IrConstructorSymbol,
val mappings: Map<IrValueParameter, List<ObjectMappingSource>>,
val unknowns: List<Pair<Name, ObjectMappingSource>>,
) : Mapping

data class EnumMapping(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.github.mappie.resolving.classes

import io.github.mappie.BaseVisitor
import io.github.mappie.MappieIrRegistrar.Companion.context
import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper
import io.github.mappie.resolving.*
import io.github.mappie.util.getterName
import io.github.mappie.util.irGet
Expand Down Expand Up @@ -49,26 +49,26 @@ class ObjectMappingBodyCollector(

override fun visitFunctionExpression(expression: IrFunctionExpression, data: ObjectMappingsConstructor): ObjectMappingsConstructor {
return expression.function.body?.statements?.fold(data) { acc, current ->
acc.let { current.accept(ObjectBodyStatementCollector(file, dispatchReceiverSymbol), Unit)?.let { acc.explicit(it) } ?: it }
acc.let { current.accept(ObjectBodyStatementCollector(file!!, dispatchReceiverSymbol), Unit)?.let { acc.explicit(it) } ?: it }
} ?: data
}
}

private class ObjectBodyStatementCollector(
file: IrFileEntry?,
file: IrFileEntry,
private val dispatchReceiverSymbol: IrValueSymbol,
) : BaseVisitor<Pair<Name, ObjectMappingSource>?, Unit>(file) {

override fun visitCall(expression: IrCall, data: Unit): Pair<Name, ObjectMappingSource>? {
return when (expression.symbol.owner.name) {
IDENTIFIER_MAPPED_FROM_PROPERTY, IDENTIFIER_MAPPED_FROM_CONSTANT -> {
val target = expression.extensionReceiver!!.accept(TargetValueCollector(), data)
val target = expression.extensionReceiver!!.accept(TargetValueCollector(file!!), data)
val source = expression.valueArguments.first()!!.accept(SourceValueCollector(dispatchReceiverSymbol), Unit)

target to source
}
IDENTIFIER_MAPPED_FROM_EXPRESSION -> {
val target = expression.extensionReceiver!!.accept(TargetValueCollector(), data)
val target = expression.extensionReceiver!!.accept(TargetValueCollector(file!!), data)
val source = expression.valueArguments.first() as IrFunctionExpression

target to ExpressionSource(
Expand Down Expand Up @@ -135,7 +135,7 @@ private class MapperReferenceCollector : BaseVisitor<IrFunctionExpression, Unit>
require(expression.origin == IrStatementOrigin.GET_PROPERTY)

return when (expression.symbol.owner.name) {
getterName(DataClassMapper<*, *>::forList.name) -> {
getterName(ObjectMapper<*, *>::forList.name) -> {
val mapper = expression.symbol.owner.parent as IrClassImpl

val function = mapper.functions
Expand Down Expand Up @@ -201,9 +201,26 @@ private class SourceValueCollector(
}
}

private class TargetValueCollector : BaseVisitor<Name, Unit>() {
private class TargetValueCollector(file: IrFileEntry) : BaseVisitor<Name, Unit>(file) {

override fun visitPropertyReference(expression: IrPropertyReference, data: Unit): Name {
return expression.symbol.owner.name
}

override fun visitCall(expression: IrCall, data: Unit): Name {
return when (expression.symbol.owner.name) {
IDENTIFIER_PARAMETER -> {
val value = expression.valueArguments.first()!!
return if (value is IrConst<*>) {
Name.identifier(value.value as String)
} else {
logError("Parameter name must be a String literal", file?.let { location(it, expression) })
throw AssertionError()
}
}
else -> {
super.visitCall(expression, data)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ObjectMappingsConstructor(val targetType: IrType, val source: IrValueParam

var constructor: IrConstructor? = null

private val targets
val targets
get() = constructor?.valueParameters ?: emptyList()

fun construct(): ConstructorCallMapping {
Expand All @@ -41,11 +41,15 @@ class ObjectMappingsConstructor(val targetType: IrType, val source: IrValueParam
}
}

val unknowns = explicit
.filter { it.first !in mappings.map { it.key.name } }

return ConstructorCallMapping(
targetType = targetType,
sourceType = source.type,
symbol = constructor!!.symbol,
mappings = mappings
mappings = mappings,
unknowns = unknowns,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ interface MappingValidation {
) }
)

addAll(
mapping.unknowns
.map { Problem.error("Parameter ${it.first.asString()} does not occur as a parameter in constructor") }
)

// TODO: make optional via configuration
if (!mapping.symbol.owner.visibility.isPublicAPI) {
add(Problem.error("Constructor is not public", location(mapping.symbol.owner)))
Expand Down
4 changes: 2 additions & 2 deletions testing/src/main/kotlin/testing/ClassMapper.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package testing

import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper

class Class(
private val field: String,
Expand All @@ -22,7 +22,7 @@ class ClassDto(
argument.hashCode()
}

object ClassMapper : DataClassMapper<Class, ClassDto>() {
object ClassMapper : ObjectMapper<Class, ClassDto>() {
override fun map(from: Class) = mapping {
ClassDto::argument mappedFromConstant 1
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package testing

import io.github.mappie.api.ObjectMapper

class ConstructorParameterWhichIsNotAField(
val parameter: String
)

class ConstructorParameterWhichIsNotAFieldDto(
value: String
) {
val property = value

override fun equals(other: Any?): Boolean {
if (other is ConstructorParameterWhichIsNotAFieldDto) {
return property == other.property
}
return false
}
}

object ConstructorParameterWhichIsNotAFieldMapper : ObjectMapper<ConstructorParameterWhichIsNotAField, ConstructorParameterWhichIsNotAFieldDto>() {
override fun map(from: ConstructorParameterWhichIsNotAField): ConstructorParameterWhichIsNotAFieldDto = mapping {
parameter("value") mappedFromProperty ConstructorParameterWhichIsNotAField::parameter
}
}
4 changes: 2 additions & 2 deletions testing/src/main/kotlin/testing/DefaultValue.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package testing

import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper

data class DefaultValue(val string: String)

data class DefaultValueDto(val string: String, val int: Int = 10)

object DefaultValueMapper : DataClassMapper<DefaultValue, DefaultValueDto>() {
object DefaultValueMapper : ObjectMapper<DefaultValue, DefaultValueDto>() {
override fun map(from: DefaultValue): DefaultValueDto = mapping()
}
4 changes: 2 additions & 2 deletions testing/src/main/kotlin/testing/ExpressionMapper.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package testing

import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper

object ExpressionMapper : DataClassMapper<Person, PersonDto>() {
object ExpressionMapper : ObjectMapper<Person, PersonDto>() {
override fun map(from: Person): PersonDto = mapping {
PersonDto::age mappedFromConstant 10
PersonDto::description mappedFromExpression { it::class.simpleName!! }
Expand Down
4 changes: 2 additions & 2 deletions testing/src/main/kotlin/testing/GameMapper.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package testing

import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper

data class Game(
val name: String,
Expand All @@ -12,7 +12,7 @@ data class GameDto(
val description: String,
)

object GameMapper : DataClassMapper<Game, GameDto>() {
object GameMapper : ObjectMapper<Game, GameDto>() {
override fun map(from: Game): GameDto = mapping {
GameDto::description mappedFromProperty Game::description transform { it ?: "default" }
}
Expand Down
6 changes: 3 additions & 3 deletions testing/src/main/kotlin/testing/ListMapper.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package testing

import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper

data class Book(val pages: List<Page>)

data class Page(val text: String)

data class BookDto(val pages: List<String>)

object BookMapper : DataClassMapper<Book, BookDto>() {
object BookMapper : ObjectMapper<Book, BookDto>() {
override fun map(from: Book): BookDto = mapping {
BookDto::pages mappedFromProperty Book::pages via PageMapper.forList
}
}

object PageMapper : DataClassMapper<Page, String>() {
object PageMapper : ObjectMapper<Page, String>() {
override fun map(from: Page): String = mapping {
result(from.text)
}
Expand Down
6 changes: 3 additions & 3 deletions testing/src/main/kotlin/testing/MultipleConstructor.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package testing

import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper

data class MultipleConstructors(val string: String)

data class MultipleConstructorsDto constructor(val string: String, val int: Int) {
constructor(string: String) : this(string, 1)
}

object MultipleConstructorsWithoutIntMapper : DataClassMapper<MultipleConstructors, MultipleConstructorsDto>() {
object MultipleConstructorsWithoutIntMapper : ObjectMapper<MultipleConstructors, MultipleConstructorsDto>() {
override fun map(from: MultipleConstructors): MultipleConstructorsDto = mapping()
}

object MultipleConstructorsWitIntMapper : DataClassMapper<MultipleConstructors, MultipleConstructorsDto>() {
object MultipleConstructorsWitIntMapper : ObjectMapper<MultipleConstructors, MultipleConstructorsDto>() {
override fun map(from: MultipleConstructors): MultipleConstructorsDto = mapping {
MultipleConstructorsDto::int mappedFromConstant 2
}
Expand Down
6 changes: 3 additions & 3 deletions testing/src/main/kotlin/testing/NestedMapper.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package testing

import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper
import io.github.mappie.api.EnumMapper

enum class BooleanEnum {
Expand All @@ -21,14 +21,14 @@ data class ThingDto(val inner: ThangDto, val boolean: BooleanDto)

data class ThangDto(val description: String)

object ThingMapper : DataClassMapper<Thing, ThingDto>() {
object ThingMapper : ObjectMapper<Thing, ThingDto>() {
override fun map(from: Thing): ThingDto = mapping {
ThingDto::inner mappedFromProperty Thing::inner via ThangMapper
ThingDto::boolean mappedFromProperty Thing::boolean via BooleanMapper()
}
}

object ThangMapper : DataClassMapper<Thang, ThangDto>() {
object ThangMapper : ObjectMapper<Thang, ThangDto>() {
override fun map(from: Thang): ThangDto = mapping()
}

Expand Down
6 changes: 3 additions & 3 deletions testing/src/main/kotlin/testing/PersonMapper.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package testing

import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper
import io.github.mappie.api.Mapper

data class Person(val name: String)

data class PersonDto(val name: String, val description: String, val age: Int)

object PersonMapper : DataClassMapper<Person, PersonDto>() {
object PersonMapper : ObjectMapper<Person, PersonDto>() {

override fun map(from: Person): PersonDto = mapping {
PersonDto::description mappedFromProperty Person::name
Expand All @@ -23,7 +23,7 @@ object ConstructorCallPersonMapper : Mapper<Person, PersonDto>() {
}
}

object TransformingPersonMapper : DataClassMapper<Person, PersonDto>() {
object TransformingPersonMapper : ObjectMapper<Person, PersonDto>() {

override fun map(from: Person): PersonDto = mapping {
PersonDto::description mappedFromProperty Person::name transform { "$it Surname" }
Expand Down
6 changes: 3 additions & 3 deletions testing/src/main/kotlin/testing/PrimitiveMapper.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package testing

import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper

object IntMapper : DataClassMapper<Int, String>() {
object IntMapper : ObjectMapper<Int, String>() {

override fun map(from: Int) = mapping {
result(from.toString())
}
}

object StringMapper : DataClassMapper<String, Int>() {
object StringMapper : ObjectMapper<String, Int>() {

override fun map(from: String) = mapping {
result(from.toInt())
Expand Down
4 changes: 2 additions & 2 deletions testing/src/main/kotlin/testing/PrivateConstructor.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package testing

import io.github.mappie.api.DataClassMapper
import io.github.mappie.api.ObjectMapper

data class PrivateConstructor(val string: String)

data class PrivateConstructorDto constructor(val string: String, val int: Int) {
private constructor(string: String) : this(string, 1)
}

object PrivateConstructorMapper : DataClassMapper<PrivateConstructor, PrivateConstructorDto>() {
object PrivateConstructorMapper : ObjectMapper<PrivateConstructor, PrivateConstructorDto>() {
override fun map(from: PrivateConstructor): PrivateConstructorDto = mapping {
PrivateConstructorDto::int mappedFromConstant 1
}
Expand Down
Loading

0 comments on commit b97874a

Please sign in to comment.