29
29
30
30
package org.jetbrains.kotlinx.spark.api
31
31
32
+ import org.apache.commons.lang3.reflect.TypeUtils.*
32
33
import org.apache.spark.sql.Encoder
33
34
import org.apache.spark.sql.Row
34
35
import org.apache.spark.sql.catalyst.DefinedByConstructorParams
35
36
import org.apache.spark.sql.catalyst.encoders.AgnosticEncoder
36
37
import org.apache.spark.sql.catalyst.encoders.AgnosticEncoders
37
38
import org.apache.spark.sql.catalyst.encoders.AgnosticEncoders.EncoderField
39
+ import org.apache.spark.sql.catalyst.encoders.AgnosticEncoders.JavaBeanEncoder
38
40
import org.apache.spark.sql.catalyst.encoders.AgnosticEncoders.ProductEncoder
39
41
import org.apache.spark.sql.catalyst.encoders.OuterScopes
40
42
import org.apache.spark.sql.types.DataType
@@ -49,19 +51,23 @@ import org.jetbrains.kotlinx.spark.api.plugin.annotations.ColumnName
49
51
import org.jetbrains.kotlinx.spark.api.plugin.annotations.Sparkify
50
52
import scala.reflect.ClassTag
51
53
import java.io.Serializable
54
+ import java.util.*
55
+ import javax.annotation.Nonnull
52
56
import kotlin.reflect.KClass
53
57
import kotlin.reflect.KMutableProperty
54
58
import kotlin.reflect.KProperty1
55
59
import kotlin.reflect.KType
56
60
import kotlin.reflect.KTypeProjection
57
61
import kotlin.reflect.full.createType
62
+ import kotlin.reflect.full.declaredMemberFunctions
58
63
import kotlin.reflect.full.declaredMemberProperties
59
64
import kotlin.reflect.full.hasAnnotation
60
65
import kotlin.reflect.full.isSubclassOf
61
66
import kotlin.reflect.full.isSubtypeOf
62
67
import kotlin.reflect.full.primaryConstructor
63
68
import kotlin.reflect.full.staticFunctions
64
69
import kotlin.reflect.full.withNullability
70
+ import kotlin.reflect.jvm.javaGetter
65
71
import kotlin.reflect.jvm.javaMethod
66
72
import kotlin.reflect.jvm.jvmName
67
73
import kotlin.reflect.typeOf
@@ -163,6 +169,7 @@ object KotlinTypeInference : Serializable {
163
169
*
164
170
* @return an [AgnosticEncoder] for the given [kType].
165
171
*/
172
+ @Suppress(" UNCHECKED_CAST" )
166
173
fun <T > encoderFor (kType : KType ): AgnosticEncoder <T > =
167
174
encoderFor(
168
175
currentType = kType,
@@ -562,10 +569,6 @@ object KotlinTypeInference : Serializable {
562
569
}
563
570
564
571
kClass.isData -> {
565
- // TODO provide warnings for non-Sparkify annotated classes
566
- // TODO especially Pair and Triple, promote people to use Tuple2 and Tuple3 or use "getFirst" etc. as column name
567
-
568
- if (currentType in seenTypeSet) throw IllegalStateException (" Circular reference detected for type $currentType " )
569
572
val constructor = kClass.primaryConstructor!!
570
573
val kParameters = constructor .parameters
571
574
// todo filter for transient?
@@ -586,7 +589,7 @@ object KotlinTypeInference : Serializable {
586
589
)
587
590
588
591
val paramName = param.name
589
- val readMethodName = prop.getter.javaMethod !! .name
592
+ val readMethodName = prop.javaGetter !! .name
590
593
val writeMethodName = (prop as ? KMutableProperty <* >)?.setter?.javaMethod?.name
591
594
592
595
EncoderField (
@@ -636,13 +639,87 @@ object KotlinTypeInference : Serializable {
636
639
}
637
640
638
641
// java bean class
639
- // currentType.classifier is KClass<*> -> {
640
- // TODO()
641
- //
642
- // JavaBeanEncoder()
643
- // }
642
+ else -> {
643
+ if (currentType in seenTypeSet)
644
+ throw IllegalStateException (" Circular reference detected for type $currentType " )
645
+
646
+ val properties = getJavaBeanReadableProperties(kClass)
647
+ val fields = properties.map {
648
+ val encoder = encoderFor(
649
+ currentType = it.type,
650
+ seenTypeSet = seenTypeSet + currentType,
651
+ typeVariables = typeVariables,
652
+ )
653
+
654
+ EncoderField (
655
+ /* name = */ it.propName,
656
+ /* enc = */ encoder,
657
+ /* nullable = */ encoder.nullable() && ! it.hasNonnull,
658
+ /* metadata = */ Metadata .empty(),
659
+ /* readMethod = */ it.getterName.toOption(),
660
+ /* writeMethod = */ it.setterName.toOption(),
661
+ )
662
+ }
663
+
664
+ JavaBeanEncoder <Any >(
665
+ ClassTag .apply (jClass),
666
+ fields.asScalaSeq(),
667
+ )
668
+ }
669
+
670
+ // else -> throw IllegalArgumentException("No encoder found for type $currentType")
671
+ }
672
+ }
644
673
645
- else -> throw IllegalArgumentException (" No encoder found for type $currentType " )
674
+ private data class JavaReadableProperty (
675
+ val propName : String ,
676
+ val getterName : String ,
677
+ val setterName : String? ,
678
+ val type : KType ,
679
+ val hasNonnull : Boolean ,
680
+ )
681
+
682
+ private fun getJavaBeanReadableProperties (klass : KClass <* >): List <JavaReadableProperty > {
683
+ val functions = klass.declaredMemberFunctions.filter {
684
+ it.name.startsWith(" get" ) || it.name.startsWith(" is" ) || it.name.startsWith(" set" )
646
685
}
686
+
687
+ val properties = functions.mapNotNull { getter ->
688
+ if (getter.name.startsWith(" set" )) return @mapNotNull null
689
+
690
+ val propName = getter.name
691
+ .removePrefix(" get" )
692
+ .removePrefix(" is" )
693
+ .replaceFirstChar { it.lowercase() }
694
+ val setter = functions.find {
695
+ it.name == " set${propName.replaceFirstChar { it.uppercase() }} "
696
+ }
697
+
698
+ JavaReadableProperty (
699
+ propName = propName,
700
+ getterName = getter.name,
701
+ setterName = setter?.name,
702
+ type = getter.returnType,
703
+ hasNonnull = getter.hasAnnotation<Nonnull >(),
704
+ )
705
+ }
706
+
707
+ // Aside from java get/set functions, attempt to get kotlin properties as well, for non data classes
708
+ val kotlinProps = klass.declaredMemberProperties
709
+ .filter { it.getter.javaMethod != null } // filter kotlin-facing props
710
+ .map {
711
+ val hasSetter = (it as ? KMutableProperty <* >)?.setter != null
712
+ val nameSuffix = it.name.removePrefix(" is" ).replaceFirstChar { it.uppercase() }
713
+
714
+ JavaReadableProperty (
715
+ propName = it.name,
716
+ getterName = if (it.name.startsWith(" is" )) it.name else " get$nameSuffix " ,
717
+ setterName = if (hasSetter) " set$nameSuffix " else null ,
718
+ type = it.returnType,
719
+ hasNonnull = it.hasAnnotation<Nonnull >(),
720
+ )
721
+ }
722
+
723
+ return properties + kotlinProps
647
724
}
648
725
}
0 commit comments