@@ -19,16 +19,20 @@ import org.jetbrains.kotlinx.dataframe.impl.columnName
1919import org.jetbrains.kotlinx.dataframe.impl.emptyPath
2020import org.jetbrains.kotlinx.dataframe.impl.getListType
2121import 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
2324import java.lang.reflect.InvocationTargetException
2425import java.lang.reflect.Method
2526import java.time.temporal.Temporal
27+ import kotlin.reflect.KCallable
2628import kotlin.reflect.KClass
2729import kotlin.reflect.KProperty
2830import kotlin.reflect.KVisibility
2931import kotlin.reflect.full.isSubclassOf
32+ import kotlin.reflect.full.memberFunctions
3033import kotlin.reflect.full.memberProperties
3134import kotlin.reflect.full.primaryConstructor
35+ import kotlin.reflect.full.valueParameters
3236import kotlin.reflect.full.withNullability
3337import kotlin.reflect.jvm.isAccessible
3438import 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
5257internal 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
127143internal 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