Skip to content

Commit 2f79074

Browse files
committed
added reflective isArray functions (with tests), so we can treat arrays as values in toDataFrame { properties() }
1 parent f3cc35c commit 2f79074

File tree

4 files changed

+174
-2
lines changed

4 files changed

+174
-2
lines changed

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@file:OptIn(ExperimentalUnsignedTypes::class)
2+
13
package org.jetbrains.kotlinx.dataframe.impl
24

35
import org.jetbrains.kotlinx.dataframe.AnyFrame
@@ -467,3 +469,104 @@ internal fun nothingType(nullable: Boolean): KType =
467469
} else {
468470
typeOf<List<Nothing>>()
469471
}.arguments.first().type!!
472+
473+
@OptIn(ExperimentalUnsignedTypes::class)
474+
private val primitiveArrayClasses = setOf(
475+
BooleanArray::class,
476+
ByteArray::class,
477+
ShortArray::class,
478+
IntArray::class,
479+
LongArray::class,
480+
FloatArray::class,
481+
DoubleArray::class,
482+
CharArray::class,
483+
484+
UByteArray::class,
485+
UShortArray::class,
486+
UIntArray::class,
487+
ULongArray::class,
488+
)
489+
490+
/**
491+
* Returns `true` if this class is a primitive array class like `XArray`.
492+
*
493+
* Use [KClass.isArray] to also check for `Array<>`.
494+
*/
495+
internal val KClass<*>.isPrimitiveArray: Boolean
496+
get() = this in primitiveArrayClasses
497+
498+
/**
499+
* Returns `true` if this class is an array, either a primitive array like `XArray` or `Array<>`.
500+
*
501+
* Use [KClass.isPrimitiveArray] to only check for primitive arrays.
502+
*/
503+
internal val KClass<*>.isArray: Boolean
504+
get() = this in primitiveArrayClasses ||
505+
this.qualifiedName == Array::class.qualifiedName // instance check fails
506+
507+
/**
508+
* Returns `true` if this type is of a primitive array like `XArray`.
509+
*
510+
* Use [KType.isArray] to also check for `Array<>`.
511+
*/
512+
internal val KType.isPrimitiveArray: Boolean
513+
get() =
514+
if (arguments.isNotEmpty()) {
515+
// Catching https://github.com/Kotlin/dataframe/issues/678
516+
// as typeOf<Array<Int>>().classifier == IntArray::class
517+
false
518+
} else {
519+
(classifier as? KClass<*>)?.isPrimitiveArray == true
520+
}
521+
522+
523+
/**
524+
* Returns `true` if this type is of an array, either a primitive array like `XArray` or `Array<>`.
525+
*
526+
* Use [KType.isPrimitiveArray] to only check for primitive arrays.
527+
*/
528+
internal val KType.isArray: Boolean
529+
get() = (classifier as? KClass<*>)?.isArray == true
530+
531+
/**
532+
* Returns `true` if this object is a primitive array like `XArray`.
533+
*
534+
* Use [Any.isArray] to also check for the `Array<>` object.
535+
*/
536+
internal val Any.isPrimitiveArray: Boolean
537+
get() = this::class.isPrimitiveArray
538+
539+
/**
540+
* Returns `true` if this object is an array, either a primitive array like `XArray` or `Array<>`.
541+
*
542+
* Use [Any.isPrimitiveArray] to only check for primitive arrays.
543+
*/
544+
internal val Any.isArray: Boolean
545+
get() = this::class.isArray
546+
547+
/**
548+
* Returns this array object as a list of values.
549+
*
550+
* @throws IllegalArgumentException if this object is not an array
551+
*/
552+
internal fun Any.asArrayToList(): List<*> {
553+
require(this.isArray) { "Not an array" }
554+
return when (this) {
555+
is BooleanArray -> toList()
556+
is ByteArray -> toList()
557+
is ShortArray -> toList()
558+
is IntArray -> toList()
559+
is LongArray -> toList()
560+
is FloatArray -> toList()
561+
is DoubleArray -> toList()
562+
is CharArray -> toList()
563+
564+
is UByteArray -> toList()
565+
is UShortArray -> toList()
566+
is UIntArray -> toList()
567+
is ULongArray -> toList()
568+
569+
is Array<*> -> toList()
570+
else -> error("Not an array")
571+
}
572+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.jetbrains.kotlinx.dataframe.impl.asList
1818
import org.jetbrains.kotlinx.dataframe.impl.columnName
1919
import org.jetbrains.kotlinx.dataframe.impl.emptyPath
2020
import org.jetbrains.kotlinx.dataframe.impl.getListType
21+
import org.jetbrains.kotlinx.dataframe.impl.isArray
2122
import org.jetbrains.kotlinx.dataframe.impl.isGetterLike
2223
import org.jetbrains.kotlinx.dataframe.impl.projectUpTo
2324
import org.jetbrains.kotlinx.dataframe.impl.schema.sortWithConstructors
@@ -51,7 +52,8 @@ internal val KClass<*>.isValueType: Boolean
5152
this in valueTypes ||
5253
this.isSubclassOf(Number::class) ||
5354
this.isSubclassOf(Enum::class) ||
54-
this.isSubclassOf(Temporal::class)
55+
this.isSubclassOf(Temporal::class) ||
56+
this.isArray
5557

5658
internal class CreateDataFrameDslImpl<T>(
5759
override val source: Iterable<T>,

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package org.jetbrains.kotlinx.dataframe.api
22

33
import io.kotest.matchers.shouldBe
4+
import org.jetbrains.kotlinx.dataframe.DataColumn
45
import org.jetbrains.kotlinx.dataframe.DataFrame
56
import org.jetbrains.kotlinx.dataframe.DataRow
6-
import org.jetbrains.kotlinx.dataframe.alsoDebug
77
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
88
import org.jetbrains.kotlinx.dataframe.columns.ColumnKind
99
import org.jetbrains.kotlinx.dataframe.kind
@@ -392,4 +392,19 @@ class CreateDataFrameTests {
392392
properties(JavaPojo::getB)
393393
} shouldBe dataFrameOf("b")("bb")
394394
}
395+
396+
data class Arrays(val a: IntArray, val b: Array<Int>, val c: Array<Int?>)
397+
398+
@Test
399+
fun `arrays in to DF`() {
400+
val df = listOf(
401+
Arrays(intArrayOf(1, 2), arrayOf(3, 4), arrayOf(5, null))
402+
).toDataFrame(maxDepth = Int.MAX_VALUE)
403+
404+
df.schema() shouldBe dataFrameOf(
405+
DataColumn.createValueColumn("a", listOf(intArrayOf(1, 2)), typeOf<IntArray>()),
406+
DataColumn.createValueColumn("b", listOf(arrayOf(3, 4)), typeOf<Array<Int>>()),
407+
DataColumn.createValueColumn("c", listOf(arrayOf(5, null)), typeOf<Array<Int?>>()),
408+
).schema()
409+
}
395410
}

core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/types/UtilTests.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package org.jetbrains.kotlinx.dataframe.types
22

33
import io.kotest.matchers.shouldBe
4+
import org.jetbrains.kotlinx.dataframe.impl.asArrayToList
45
import org.jetbrains.kotlinx.dataframe.impl.commonParent
56
import org.jetbrains.kotlinx.dataframe.impl.commonParents
67
import org.jetbrains.kotlinx.dataframe.impl.commonType
78
import org.jetbrains.kotlinx.dataframe.impl.commonTypeListifyValues
89
import org.jetbrains.kotlinx.dataframe.impl.createType
910
import org.jetbrains.kotlinx.dataframe.impl.guessValueType
11+
import org.jetbrains.kotlinx.dataframe.impl.isArray
12+
import org.jetbrains.kotlinx.dataframe.impl.isPrimitiveArray
1013
import org.jetbrains.kotlinx.dataframe.impl.nothingType
1114
import org.jetbrains.kotlinx.dataframe.impl.replaceGenericTypeParametersWithUpperbound
1215
import org.junit.Test
@@ -17,6 +20,55 @@ import kotlin.reflect.typeOf
1720

1821
class UtilTests {
1922

23+
@OptIn(ExperimentalUnsignedTypes::class)
24+
@Test
25+
fun `isArray tests`() {
26+
// KClass isArray
27+
BooleanArray::class.isArray shouldBe true
28+
UIntArray::class.isArray shouldBe true
29+
Array::class.isArray shouldBe true
30+
31+
// KClass isPrimitiveArray
32+
BooleanArray::class.isPrimitiveArray shouldBe true
33+
UIntArray::class.isPrimitiveArray shouldBe true
34+
Array::class.isPrimitiveArray shouldBe false
35+
36+
// KType isArray
37+
typeOf<BooleanArray>().isArray shouldBe true
38+
typeOf<UIntArray>().isArray shouldBe true
39+
typeOf<Array<Int>>().isArray shouldBe true
40+
typeOf<Array<Int?>>().isArray shouldBe true
41+
typeOf<Array<*>>().isArray shouldBe true
42+
43+
// KType isPrimitiveArray
44+
typeOf<BooleanArray>().isPrimitiveArray shouldBe true
45+
typeOf<UIntArray>().isPrimitiveArray shouldBe true
46+
typeOf<Array<Int>>().isPrimitiveArray shouldBe false
47+
typeOf<Array<Int?>>().isPrimitiveArray shouldBe false
48+
typeOf<Array<*>>().isPrimitiveArray shouldBe false
49+
50+
// Any isArray
51+
booleanArrayOf().isArray shouldBe true
52+
uintArrayOf().isArray shouldBe true
53+
arrayOf(1).isArray shouldBe true
54+
arrayOf(1, null).isArray shouldBe true
55+
arrayOfNulls<Any?>(1).isArray shouldBe true
56+
57+
// Any isPrimitiveArray
58+
booleanArrayOf().isPrimitiveArray shouldBe true
59+
uintArrayOf().isPrimitiveArray shouldBe true
60+
arrayOf(1).isPrimitiveArray shouldBe false
61+
arrayOf(1, null).isPrimitiveArray shouldBe false
62+
arrayOfNulls<Any?>(1).isPrimitiveArray shouldBe false
63+
64+
// Any asArrayToList
65+
booleanArrayOf(true, false).asArrayToList() shouldBe listOf(true, false)
66+
uintArrayOf(1u, 2u).asArrayToList() shouldBe listOf(1u, 2u)
67+
arrayOf(1, 2).asArrayToList() shouldBe listOf(1, 2)
68+
arrayOf(1, null).asArrayToList() shouldBe listOf(1, null)
69+
arrayOfNulls<Any?>(1).asArrayToList() shouldBe listOf(null)
70+
}
71+
2072
@Test
2173
fun commonParentsTests() {
2274
commonParents(Int::class, Int::class) shouldBe listOf(Int::class)

0 commit comments

Comments
 (0)