Skip to content

Commit b82d08b

Browse files
committed
relaxing toDataFrame properties DSL to allow for getter functions too. If no memberProperties are available, fall back to getter-like memberFunctions. Also, it now tries to sort by any constructor if there are multiple (common in java with a no-arg constructor)
1 parent 8ff090b commit b82d08b

File tree

5 files changed

+90
-29
lines changed

5 files changed

+90
-29
lines changed

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/toDataFrame.kt

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import org.jetbrains.kotlinx.dataframe.impl.asList
1313
import org.jetbrains.kotlinx.dataframe.impl.columnName
1414
import org.jetbrains.kotlinx.dataframe.impl.columns.guessColumnType
1515
import org.jetbrains.kotlinx.dataframe.index
16+
import kotlin.reflect.KCallable
1617
import kotlin.reflect.KClass
1718
import kotlin.reflect.KProperty
1819

@@ -115,24 +116,26 @@ public fun Iterable<Pair<String, Iterable<Any?>>>.toDataFrameFromPairs(): AnyFra
115116
public interface TraversePropertiesDsl {
116117

117118
/**
118-
* Skip given [classes] during recursive (dfs) traversal
119+
* Skip given [classes] during recursive (dfs) traversal.
119120
*/
120121
public fun exclude(vararg classes: KClass<*>)
121122

122123
/**
123-
* Skip given [properties] during recursive (dfs) traversal
124+
* Skip given [properties] during recursive (dfs) traversal.
125+
* These can also be getter-like functions.
124126
*/
125-
public fun exclude(vararg properties: KProperty<*>)
127+
public fun exclude(vararg properties: KCallable<*>)
126128

127129
/**
128-
* Store given [classes] in ValueColumns without transformation into ColumnGroups or FrameColumns
130+
* Store given [classes] in ValueColumns without transformation into ColumnGroups or FrameColumns.
129131
*/
130132
public fun preserve(vararg classes: KClass<*>)
131133

132134
/**
133-
* Store given [properties] in ValueColumns without transformation into ColumnGroups or FrameColumns
135+
* Store given [properties] in ValueColumns without transformation into ColumnGroups or FrameColumns.
136+
* These can also be getter-like functions.
134137
*/
135-
public fun preserve(vararg properties: KProperty<*>)
138+
public fun preserve(vararg properties: KCallable<*>)
136139
}
137140

138141
public inline fun <reified T> TraversePropertiesDsl.preserve(): Unit = preserve(T::class)
@@ -148,7 +151,7 @@ public abstract class CreateDataFrameDsl<T> : TraversePropertiesDsl {
148151
public infix fun AnyBaseCol.into(path: ColumnPath): Unit = add(this, path)
149152

150153
public abstract fun properties(
151-
vararg roots: KProperty<*>,
154+
vararg roots: KCallable<*>,
152155
maxDepth: Int = 0,
153156
body: (TraversePropertiesDsl.() -> Unit)? = null,
154157
)

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/MarkersExtractor.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import org.jetbrains.kotlinx.dataframe.DataFrame
44
import org.jetbrains.kotlinx.dataframe.DataRow
55
import org.jetbrains.kotlinx.dataframe.annotations.ColumnName
66
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
7-
import org.jetbrains.kotlinx.dataframe.impl.schema.getPropertiesOrder
7+
import org.jetbrains.kotlinx.dataframe.impl.schema.getPropertyOrderFromPrimaryConstructor
88
import org.jetbrains.kotlinx.dataframe.schema.ColumnSchema
99
import kotlin.reflect.KClass
1010
import kotlin.reflect.KType
@@ -53,7 +53,7 @@ internal object MarkersExtractor {
5353
}
5454

5555
private fun getFields(markerClass: KClass<*>, nullableProperties: Boolean): List<GeneratedField> {
56-
val order = getPropertiesOrder(markerClass)
56+
val order = getPropertyOrderFromPrimaryConstructor(markerClass) ?: emptyMap()
5757
return markerClass.memberProperties.sortedBy { order[it.name] ?: Int.MAX_VALUE }.mapIndexed { _, it ->
5858
val fieldName = ValidFieldName.of(it.name)
5959
val columnName = it.findAnnotation<ColumnName>()?.name ?: fieldName.unquoted

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/Utils.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import org.jetbrains.kotlinx.dataframe.impl.columns.toColumnSet
1414
import org.jetbrains.kotlinx.dataframe.nrow
1515
import java.math.BigDecimal
1616
import java.math.BigInteger
17+
import kotlin.reflect.KCallable
1718
import kotlin.reflect.KClass
18-
import kotlin.reflect.KProperty
19+
import kotlin.reflect.KFunction
1920
import kotlin.reflect.KType
2021
import kotlin.reflect.KTypeProjection
2122
import kotlin.reflect.KVariance
@@ -338,4 +339,15 @@ internal fun List<String>.joinToCamelCaseString(): String {
338339
}
339340

340341
@PublishedApi
341-
internal val <T> KProperty<T>.columnName: String get() = findAnnotation<ColumnName>()?.name ?: name
342+
internal val <T> KCallable<T>.columnName: String
343+
get() = findAnnotation<ColumnName>()?.name
344+
?: when (this) {
345+
// for defining the column names based on a getter-function, we use the function name minus the get/is prefix
346+
is KFunction<*> ->
347+
name
348+
.removePrefix("get")
349+
.removePrefix("is")
350+
.replaceFirstChar { it.lowercase() }
351+
352+
else -> name
353+
}

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/toDataFrame.kt

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@ import org.jetbrains.kotlinx.dataframe.impl.columnName
1919
import org.jetbrains.kotlinx.dataframe.impl.emptyPath
2020
import org.jetbrains.kotlinx.dataframe.impl.getListType
2121
import org.jetbrains.kotlinx.dataframe.impl.projectUpTo
22-
import org.jetbrains.kotlinx.dataframe.impl.schema.getPropertiesOrder
22+
import org.jetbrains.kotlinx.dataframe.impl.schema.getPropertyOrderFromAnyConstructor
23+
import org.jetbrains.kotlinx.dataframe.impl.schema.getPropertyOrderFromPrimaryConstructor
2324
import java.lang.reflect.InvocationTargetException
2425
import java.lang.reflect.Method
2526
import java.time.temporal.Temporal
27+
import kotlin.reflect.KCallable
2628
import kotlin.reflect.KClass
2729
import kotlin.reflect.KProperty
2830
import kotlin.reflect.KVisibility
2931
import kotlin.reflect.full.isSubclassOf
32+
import kotlin.reflect.full.memberFunctions
3033
import kotlin.reflect.full.memberProperties
3134
import kotlin.reflect.full.primaryConstructor
35+
import kotlin.reflect.full.valueParameters
3236
import kotlin.reflect.full.withNullability
3337
import kotlin.reflect.jvm.isAccessible
3438
import kotlin.reflect.jvm.javaField
@@ -72,13 +76,13 @@ internal class CreateDataFrameDslImpl<T>(
7276

7377
internal class TraverseConfiguration : TraversePropertiesDsl {
7478

75-
val excludeProperties = mutableSetOf<KProperty<*>>()
79+
val excludeProperties = mutableSetOf<KCallable<*>>()
7680

7781
val excludeClasses = mutableSetOf<KClass<*>>()
7882

7983
val preserveClasses = mutableSetOf<KClass<*>>()
8084

81-
val preserveProperties = mutableSetOf<KProperty<*>>()
85+
val preserveProperties = mutableSetOf<KCallable<*>>()
8286

8387
fun clone(): TraverseConfiguration = TraverseConfiguration().also {
8488
it.excludeClasses.addAll(excludeClasses)
@@ -87,7 +91,7 @@ internal class CreateDataFrameDslImpl<T>(
8791
it.preserveClasses.addAll(preserveClasses)
8892
}
8993

90-
override fun exclude(vararg properties: KProperty<*>) {
94+
override fun exclude(vararg properties: KCallable<*>) {
9195
excludeProperties.addAll(properties)
9296
}
9397

@@ -99,12 +103,12 @@ internal class CreateDataFrameDslImpl<T>(
99103
preserveClasses.addAll(classes)
100104
}
101105

102-
override fun preserve(vararg properties: KProperty<*>) {
106+
override fun preserve(vararg properties: KCallable<*>) {
103107
preserveProperties.addAll(properties)
104108
}
105109
}
106110

107-
override fun properties(vararg roots: KProperty<*>, maxDepth: Int, body: (TraversePropertiesDsl.() -> Unit)?) {
111+
override fun properties(vararg roots: KCallable<*>, maxDepth: Int, body: (TraversePropertiesDsl.() -> Unit)?) {
108112
val dsl = configuration.clone()
109113
if (body != null) {
110114
body(dsl)
@@ -127,18 +131,47 @@ internal fun <T> Iterable<T>.createDataFrameImpl(clazz: KClass<*>, body: CreateD
127131
internal fun convertToDataFrame(
128132
data: Iterable<*>,
129133
clazz: KClass<*>,
130-
roots: List<KProperty<*>>,
131-
excludes: Set<KProperty<*>>,
134+
roots: List<KCallable<*>>,
135+
excludes: Set<KCallable<*>>,
132136
preserveClasses: Set<KClass<*>>,
133-
preserveProperties: Set<KProperty<*>>,
134-
maxDepth: Int
137+
preserveProperties: Set<KCallable<*>>,
138+
maxDepth: Int,
135139
): AnyFrame {
136-
val order = getPropertiesOrder(clazz)
140+
val primaryConstructorOrder = getPropertyOrderFromPrimaryConstructor(clazz)
141+
val anyConstructorOrder = getPropertyOrderFromAnyConstructor(clazz)
137142

138-
val properties = roots.ifEmpty {
139-
clazz.memberProperties
140-
.filter { it.visibility == KVisibility.PUBLIC && it.parameters.toList().size == 1 }
141-
}.sortedBy { order[it.name] ?: Int.MAX_VALUE }
143+
val properties: List<KCallable<*>> = roots
144+
.ifEmpty {
145+
clazz.memberProperties
146+
.filter { it.visibility == KVisibility.PUBLIC && it.valueParameters.isEmpty() }
147+
}
148+
149+
// fall back to getter functions for pojo-like classes
150+
.ifEmpty {
151+
clazz.memberFunctions
152+
.filter {
153+
it.visibility == KVisibility.PUBLIC &&
154+
it.valueParameters.isEmpty() &&
155+
(it.name.startsWith("get") || it.name.startsWith("is"))
156+
}
157+
}
158+
159+
// sort properties by order of primary-ish constructor
160+
.let {
161+
val names = it.map { it.columnName }.toSet()
162+
163+
// use the primary constructor order if it's available,
164+
// else try to find a constructor that matches all properties
165+
val order = primaryConstructorOrder
166+
?: anyConstructorOrder.firstOrNull { map ->
167+
names.all { it in map.keys }
168+
}
169+
if (order != null) {
170+
it.sortedBy { order[it.columnName] ?: Int.MAX_VALUE }
171+
} else {
172+
it
173+
}
174+
}
142175

143176
val columns = properties.mapNotNull {
144177
val property = it
@@ -162,7 +195,7 @@ internal fun convertToDataFrame(
162195
valueClassConverter
163196
}
164197
}
165-
property.javaField?.isAccessible = true
198+
(property as? KProperty<*>)?.javaField?.isAccessible = true
166199
property.isAccessible = true
167200

168201
var nullable = false

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/schema/Utils.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,18 @@ internal fun DataFrameSchema.createEmptyDataFrame(numberOfRows: Int): AnyFrame =
141141
internal fun createEmptyDataFrameOf(clazz: KClass<*>): AnyFrame =
142142
MarkersExtractor.get(clazz).schema.createEmptyDataFrame()
143143

144-
internal fun getPropertiesOrder(clazz: KClass<*>): Map<String, Int> =
145-
clazz.primaryConstructor?.parameters?.mapNotNull { it.name }?.mapIndexed { i, v -> v to i }?.toMap() ?: emptyMap()
144+
internal fun getPropertyOrderFromPrimaryConstructor(clazz: KClass<*>): Map<String, Int>? =
145+
clazz.primaryConstructor
146+
?.parameters
147+
?.mapNotNull { it.name }
148+
?.mapIndexed { i, v -> v to i }
149+
?.toMap()
150+
151+
internal fun getPropertyOrderFromAnyConstructor(clazz: KClass<*>): List<Map<String, Int>> =
152+
clazz.constructors
153+
.map { constructor ->
154+
constructor.parameters
155+
.mapNotNull { it.name }
156+
.mapIndexed { i, v -> v to i }
157+
.toMap()
158+
}

0 commit comments

Comments
 (0)