@@ -19,16 +19,20 @@ import org.jetbrains.kotlinx.dataframe.impl.columnName
19
19
import org.jetbrains.kotlinx.dataframe.impl.emptyPath
20
20
import org.jetbrains.kotlinx.dataframe.impl.getListType
21
21
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
23
24
import java.lang.reflect.InvocationTargetException
24
25
import java.lang.reflect.Method
25
26
import java.time.temporal.Temporal
27
+ import kotlin.reflect.KCallable
26
28
import kotlin.reflect.KClass
27
29
import kotlin.reflect.KProperty
28
30
import kotlin.reflect.KVisibility
29
31
import kotlin.reflect.full.isSubclassOf
32
+ import kotlin.reflect.full.memberFunctions
30
33
import kotlin.reflect.full.memberProperties
31
34
import kotlin.reflect.full.primaryConstructor
35
+ import kotlin.reflect.full.valueParameters
32
36
import kotlin.reflect.full.withNullability
33
37
import kotlin.reflect.jvm.isAccessible
34
38
import kotlin.reflect.jvm.javaField
@@ -43,11 +47,12 @@ internal val valueTypes = setOf(
43
47
kotlinx.datetime.Instant ::class ,
44
48
)
45
49
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 )
51
56
52
57
internal class CreateDataFrameDslImpl <T >(
53
58
override val source : Iterable <T >,
@@ -72,13 +77,13 @@ internal class CreateDataFrameDslImpl<T>(
72
77
73
78
internal class TraverseConfiguration : TraversePropertiesDsl {
74
79
75
- val excludeProperties = mutableSetOf<KProperty <* >>()
80
+ val excludeProperties = mutableSetOf<KCallable <* >>()
76
81
77
82
val excludeClasses = mutableSetOf<KClass <* >>()
78
83
79
84
val preserveClasses = mutableSetOf<KClass <* >>()
80
85
81
- val preserveProperties = mutableSetOf<KProperty <* >>()
86
+ val preserveProperties = mutableSetOf<KCallable <* >>()
82
87
83
88
fun clone (): TraverseConfiguration = TraverseConfiguration ().also {
84
89
it.excludeClasses.addAll(excludeClasses)
@@ -87,7 +92,7 @@ internal class CreateDataFrameDslImpl<T>(
87
92
it.preserveClasses.addAll(preserveClasses)
88
93
}
89
94
90
- override fun exclude (vararg properties : KProperty <* >) {
95
+ override fun exclude (vararg properties : KCallable <* >) {
91
96
excludeProperties.addAll(properties)
92
97
}
93
98
@@ -99,25 +104,36 @@ internal class CreateDataFrameDslImpl<T>(
99
104
preserveClasses.addAll(classes)
100
105
}
101
106
102
- override fun preserve (vararg properties : KProperty <* >) {
107
+ override fun preserve (vararg properties : KCallable <* >) {
103
108
preserveProperties.addAll(properties)
104
109
}
105
110
}
106
111
107
- override fun properties (vararg roots : KProperty <* >, maxDepth : Int , body : (TraversePropertiesDsl .() -> Unit )? ) {
112
+ override fun properties (vararg roots : KCallable <* >, maxDepth : Int , body : (TraversePropertiesDsl .() -> Unit )? ) {
108
113
val dsl = configuration.clone()
109
114
if (body != null ) {
110
115
body(dsl)
111
116
}
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
+ )
113
126
df.columns().forEach {
114
127
add(it)
115
128
}
116
129
}
117
130
}
118
131
119
132
@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 > {
121
137
val builder = CreateDataFrameDslImpl (this , clazz)
122
138
builder.body()
123
139
return builder.columns.toDataFrameFromPairs()
@@ -127,18 +143,47 @@ internal fun <T> Iterable<T>.createDataFrameImpl(clazz: KClass<*>, body: CreateD
127
143
internal fun convertToDataFrame (
128
144
data : Iterable <* >,
129
145
clazz : KClass <* >,
130
- roots : List <KProperty <* >>,
131
- excludes : Set <KProperty <* >>,
146
+ roots : List <KCallable <* >>,
147
+ excludes : Set <KCallable <* >>,
132
148
preserveClasses : Set <KClass <* >>,
133
- preserveProperties : Set <KProperty <* >>,
134
- maxDepth : Int
149
+ preserveProperties : Set <KCallable <* >>,
150
+ maxDepth : Int ,
135
151
): 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()
137
174
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
+ }
142
187
143
188
val columns = properties.mapNotNull {
144
189
val property = it
@@ -148,8 +193,9 @@ internal fun convertToDataFrame(
148
193
149
194
val valueClassConverter = (it.returnType.classifier as ? KClass <* >)?.let { kClass ->
150
195
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
+ }
153
199
val parameter = constructor .parameters.singleOrNull()
154
200
? : error(" conversion of value class $kClass with multiple parameters in constructor is not yet supported" )
155
201
// there's no need to unwrap if underlying field is nullable
@@ -162,7 +208,7 @@ internal fun convertToDataFrame(
162
208
valueClassConverter
163
209
}
164
210
}
165
- property.javaField?.isAccessible = true
211
+ ( property as ? KProperty < * >)? .javaField?.isAccessible = true
166
212
property.isAccessible = true
167
213
168
214
var nullable = false
@@ -208,18 +254,34 @@ internal fun convertToDataFrame(
208
254
val kclass = (returnType.classifier as KClass <* >)
209
255
when {
210
256
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
+
215
281
kclass.isSubclassOf(Iterable ::class ) -> {
216
282
val elementType = returnType.projectUpTo(Iterable ::class ).arguments.firstOrNull()?.type
217
283
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))
223
285
} else {
224
286
val elementClass = (elementType.classifier as ? KClass <* >)
225
287
@@ -231,33 +293,55 @@ internal fun convertToDataFrame(
231
293
232
294
DataColumn .createWithTypeInference(it.columnName, listValues)
233
295
}
296
+
234
297
elementClass.isValueType -> {
235
298
val listType = getListType(elementType).withNullability(nullable)
236
299
val listValues = values.map {
237
300
(it as ? Iterable <* >)?.asList()
238
301
}
239
302
DataColumn .createValueColumn(it.columnName, listValues, listType)
240
303
}
304
+
241
305
else -> {
242
306
val frames = values.map {
243
- if (it == null ) DataFrame .empty()
244
- else {
307
+ if (it == null ) {
308
+ DataFrame .empty()
309
+ } else {
245
310
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
+ )
247
320
}
248
321
}
249
322
DataColumn .createFrameColumn(it.columnName, frames)
250
323
}
251
324
}
252
325
}
253
326
}
327
+
254
328
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)
257
339
}
258
340
}
259
341
}
260
342
return if (columns.isEmpty()) {
261
343
DataFrame .empty(data.count())
262
- } else dataFrameOf(columns)
344
+ } else {
345
+ dataFrameOf(columns)
346
+ }
263
347
}
0 commit comments