Skip to content

Commit 1dd6151

Browse files
committed
linting and generating sources
1 parent b82d08b commit 1dd6151

File tree

8 files changed

+358
-77
lines changed

8 files changed

+358
-77
lines changed

core/generated-sources/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/generated-sources/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/generated-sources/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/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/toDataFrame.kt

Lines changed: 124 additions & 40 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
@@ -43,11 +47,12 @@ internal val valueTypes = setOf(
4347
kotlinx.datetime.Instant::class,
4448
)
4549

46-
internal val KClass<*>.isValueType: Boolean get() =
47-
this in valueTypes ||
48-
this.isSubclassOf(Number::class) ||
49-
this.isSubclassOf(Enum::class) ||
50-
this.isSubclassOf(Temporal::class)
50+
internal val KClass<*>.isValueType: Boolean
51+
get() =
52+
this in valueTypes ||
53+
this.isSubclassOf(Number::class) ||
54+
this.isSubclassOf(Enum::class) ||
55+
this.isSubclassOf(Temporal::class)
5156

5257
internal class CreateDataFrameDslImpl<T>(
5358
override val source: Iterable<T>,
@@ -72,13 +77,13 @@ internal class CreateDataFrameDslImpl<T>(
7277

7378
internal class TraverseConfiguration : TraversePropertiesDsl {
7479

75-
val excludeProperties = mutableSetOf<KProperty<*>>()
80+
val excludeProperties = mutableSetOf<KCallable<*>>()
7681

7782
val excludeClasses = mutableSetOf<KClass<*>>()
7883

7984
val preserveClasses = mutableSetOf<KClass<*>>()
8085

81-
val preserveProperties = mutableSetOf<KProperty<*>>()
86+
val preserveProperties = mutableSetOf<KCallable<*>>()
8287

8388
fun clone(): TraverseConfiguration = TraverseConfiguration().also {
8489
it.excludeClasses.addAll(excludeClasses)
@@ -87,7 +92,7 @@ internal class CreateDataFrameDslImpl<T>(
8792
it.preserveClasses.addAll(preserveClasses)
8893
}
8994

90-
override fun exclude(vararg properties: KProperty<*>) {
95+
override fun exclude(vararg properties: KCallable<*>) {
9196
excludeProperties.addAll(properties)
9297
}
9398

@@ -99,25 +104,36 @@ internal class CreateDataFrameDslImpl<T>(
99104
preserveClasses.addAll(classes)
100105
}
101106

102-
override fun preserve(vararg properties: KProperty<*>) {
107+
override fun preserve(vararg properties: KCallable<*>) {
103108
preserveProperties.addAll(properties)
104109
}
105110
}
106111

107-
override fun properties(vararg roots: KProperty<*>, maxDepth: Int, body: (TraversePropertiesDsl.() -> Unit)?) {
112+
override fun properties(vararg roots: KCallable<*>, maxDepth: Int, body: (TraversePropertiesDsl.() -> Unit)?) {
108113
val dsl = configuration.clone()
109114
if (body != null) {
110115
body(dsl)
111116
}
112-
val df = convertToDataFrame(source, clazz, roots.toList(), dsl.excludeProperties, dsl.preserveClasses, dsl.preserveProperties, maxDepth)
117+
val df = convertToDataFrame(
118+
data = source,
119+
clazz = clazz,
120+
roots = roots.toList(),
121+
excludes = dsl.excludeProperties,
122+
preserveClasses = dsl.preserveClasses,
123+
preserveProperties = dsl.preserveProperties,
124+
maxDepth = maxDepth,
125+
)
113126
df.columns().forEach {
114127
add(it)
115128
}
116129
}
117130
}
118131

119132
@PublishedApi
120-
internal fun <T> Iterable<T>.createDataFrameImpl(clazz: KClass<*>, body: CreateDataFrameDslImpl<T>.() -> Unit): DataFrame<T> {
133+
internal fun <T> Iterable<T>.createDataFrameImpl(
134+
clazz: KClass<*>,
135+
body: CreateDataFrameDslImpl<T>.() -> Unit,
136+
): DataFrame<T> {
121137
val builder = CreateDataFrameDslImpl(this, clazz)
122138
builder.body()
123139
return builder.columns.toDataFrameFromPairs()
@@ -127,18 +143,47 @@ internal fun <T> Iterable<T>.createDataFrameImpl(clazz: KClass<*>, body: CreateD
127143
internal fun convertToDataFrame(
128144
data: Iterable<*>,
129145
clazz: KClass<*>,
130-
roots: List<KProperty<*>>,
131-
excludes: Set<KProperty<*>>,
146+
roots: List<KCallable<*>>,
147+
excludes: Set<KCallable<*>>,
132148
preserveClasses: Set<KClass<*>>,
133-
preserveProperties: Set<KProperty<*>>,
134-
maxDepth: Int
149+
preserveProperties: Set<KCallable<*>>,
150+
maxDepth: Int,
135151
): AnyFrame {
136-
val order = getPropertiesOrder(clazz)
152+
val primaryConstructorOrder = getPropertyOrderFromPrimaryConstructor(clazz)
153+
val anyConstructorOrder = getPropertyOrderFromAnyConstructor(clazz)
154+
155+
val properties: List<KCallable<*>> = roots
156+
.ifEmpty {
157+
clazz.memberProperties
158+
.filter { it.visibility == KVisibility.PUBLIC && it.valueParameters.isEmpty() }
159+
}
160+
161+
// fall back to getter functions for pojo-like classes
162+
.ifEmpty {
163+
clazz.memberFunctions
164+
.filter {
165+
it.visibility == KVisibility.PUBLIC &&
166+
it.valueParameters.isEmpty() &&
167+
(it.name.startsWith("get") || it.name.startsWith("is"))
168+
}
169+
}
170+
171+
// sort properties by order of primary-ish constructor
172+
.let {
173+
val names = it.map { it.columnName }.toSet()
137174

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 }
175+
// use the primary constructor order if it's available,
176+
// else try to find a constructor that matches all properties
177+
val order = primaryConstructorOrder
178+
?: anyConstructorOrder.firstOrNull { map ->
179+
names.all { it in map.keys }
180+
}
181+
if (order != null) {
182+
it.sortedBy { order[it.columnName] ?: Int.MAX_VALUE }
183+
} else {
184+
it
185+
}
186+
}
142187

143188
val columns = properties.mapNotNull {
144189
val property = it
@@ -148,8 +193,9 @@ internal fun convertToDataFrame(
148193

149194
val valueClassConverter = (it.returnType.classifier as? KClass<*>)?.let { kClass ->
150195
if (!kClass.isValue) null else {
151-
val constructor =
152-
requireNotNull(kClass.primaryConstructor) { "value class $kClass is expected to have primary constructor, but couldn't obtain it" }
196+
val constructor = requireNotNull(kClass.primaryConstructor) {
197+
"value class $kClass is expected to have primary constructor, but couldn't obtain it"
198+
}
153199
val parameter = constructor.parameters.singleOrNull()
154200
?: error("conversion of value class $kClass with multiple parameters in constructor is not yet supported")
155201
// there's no need to unwrap if underlying field is nullable
@@ -162,7 +208,7 @@ internal fun convertToDataFrame(
162208
valueClassConverter
163209
}
164210
}
165-
property.javaField?.isAccessible = true
211+
(property as? KProperty<*>)?.javaField?.isAccessible = true
166212
property.isAccessible = true
167213

168214
var nullable = false
@@ -208,18 +254,34 @@ internal fun convertToDataFrame(
208254
val kclass = (returnType.classifier as KClass<*>)
209255
when {
210256
hasExceptions -> DataColumn.createWithTypeInference(it.columnName, values, nullable)
211-
kclass == Any::class || preserveClasses.contains(kclass) || preserveProperties.contains(property) || (maxDepth <= 0 && !returnType.shouldBeConvertedToFrameColumn() && !returnType.shouldBeConvertedToColumnGroup()) || kclass.isValueType ->
212-
DataColumn.createValueColumn(it.columnName, values, returnType.withNullability(nullable))
213-
kclass == DataFrame::class && !nullable -> DataColumn.createFrameColumn(it.columnName, values as List<AnyFrame>)
214-
kclass == DataRow::class -> DataColumn.createColumnGroup(it.columnName, (values as List<AnyRow>).concat())
257+
258+
kclass == Any::class ||
259+
preserveClasses.contains(kclass) ||
260+
preserveProperties.contains(property) ||
261+
(maxDepth <= 0 && !returnType.shouldBeConvertedToFrameColumn() && !returnType.shouldBeConvertedToColumnGroup()) ||
262+
kclass.isValueType ->
263+
DataColumn.createValueColumn(
264+
name = it.columnName,
265+
values = values,
266+
type = returnType.withNullability(nullable),
267+
)
268+
269+
kclass == DataFrame::class && !nullable ->
270+
DataColumn.createFrameColumn(
271+
name = it.columnName,
272+
groups = values as List<AnyFrame>
273+
)
274+
275+
kclass == DataRow::class ->
276+
DataColumn.createColumnGroup(
277+
name = it.columnName,
278+
df = (values as List<AnyRow>).concat(),
279+
)
280+
215281
kclass.isSubclassOf(Iterable::class) -> {
216282
val elementType = returnType.projectUpTo(Iterable::class).arguments.firstOrNull()?.type
217283
if (elementType == null) {
218-
DataColumn.createValueColumn(
219-
it.columnName,
220-
values,
221-
returnType.withNullability(nullable)
222-
)
284+
DataColumn.createValueColumn(it.columnName, values, returnType.withNullability(nullable))
223285
} else {
224286
val elementClass = (elementType.classifier as? KClass<*>)
225287

@@ -231,33 +293,55 @@ internal fun convertToDataFrame(
231293

232294
DataColumn.createWithTypeInference(it.columnName, listValues)
233295
}
296+
234297
elementClass.isValueType -> {
235298
val listType = getListType(elementType).withNullability(nullable)
236299
val listValues = values.map {
237300
(it as? Iterable<*>)?.asList()
238301
}
239302
DataColumn.createValueColumn(it.columnName, listValues, listType)
240303
}
304+
241305
else -> {
242306
val frames = values.map {
243-
if (it == null) DataFrame.empty()
244-
else {
307+
if (it == null) {
308+
DataFrame.empty()
309+
} else {
245310
require(it is Iterable<*>)
246-
convertToDataFrame(it, elementClass, emptyList(), excludes, preserveClasses, preserveProperties, maxDepth - 1)
311+
convertToDataFrame(
312+
data = it,
313+
clazz = elementClass,
314+
roots = emptyList(),
315+
excludes = excludes,
316+
preserveClasses = preserveClasses,
317+
preserveProperties = preserveProperties,
318+
maxDepth = maxDepth - 1,
319+
)
247320
}
248321
}
249322
DataColumn.createFrameColumn(it.columnName, frames)
250323
}
251324
}
252325
}
253326
}
327+
254328
else -> {
255-
val df = convertToDataFrame(values, kclass, emptyList(), excludes, preserveClasses, preserveProperties, maxDepth - 1)
256-
DataColumn.createColumnGroup(it.columnName, df)
329+
val df = convertToDataFrame(
330+
data = values,
331+
clazz = kclass,
332+
roots = emptyList(),
333+
excludes = excludes,
334+
preserveClasses = preserveClasses,
335+
preserveProperties = preserveProperties,
336+
maxDepth = maxDepth - 1,
337+
)
338+
DataColumn.createColumnGroup(name = it.columnName, df = df)
257339
}
258340
}
259341
}
260342
return if (columns.isEmpty()) {
261343
DataFrame.empty(data.count())
262-
} else dataFrameOf(columns)
344+
} else {
345+
dataFrameOf(columns)
346+
}
263347
}

0 commit comments

Comments
 (0)