18
18
19
19
import java .beans .PropertyDescriptor ;
20
20
import java .beans .PropertyEditor ;
21
+ import java .lang .annotation .Annotation ;
21
22
import java .lang .reflect .Constructor ;
22
23
import java .lang .reflect .InvocationTargetException ;
23
24
import java .lang .reflect .Method ;
27
28
import java .util .Arrays ;
28
29
import java .util .Collections ;
29
30
import java .util .Date ;
31
+ import java .util .HashMap ;
30
32
import java .util .List ;
31
33
import java .util .Locale ;
34
+ import java .util .Map ;
32
35
import java .util .Set ;
33
36
37
+ import kotlin .jvm .JvmClassMappingKt ;
38
+ import kotlin .reflect .KFunction ;
39
+ import kotlin .reflect .KParameter ;
40
+ import kotlin .reflect .full .KClasses ;
41
+ import kotlin .reflect .jvm .ReflectJvmMapping ;
34
42
import org .apache .commons .logging .Log ;
35
43
import org .apache .commons .logging .LogFactory ;
36
44
53
61
* @author Juergen Hoeller
54
62
* @author Rob Harrop
55
63
* @author Sam Brannen
64
+ * @author Sebastien Deleuze
56
65
*/
57
66
public abstract class BeanUtils {
58
67
@@ -61,6 +70,9 @@ public abstract class BeanUtils {
61
70
private static final Set <Class <?>> unknownEditorTypes =
62
71
Collections .newSetFromMap (new ConcurrentReferenceHashMap <>(64 ));
63
72
73
+ private static final boolean kotlinPresent =
74
+ ClassUtils .isPresent ("kotlin.Unit" , BeanUtils .class .getClassLoader ());
75
+
64
76
65
77
/**
66
78
* Convenience method to instantiate a class using its no-arg constructor.
@@ -103,7 +115,12 @@ public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationExc
103
115
throw new BeanInstantiationException (clazz , "Specified class is an interface" );
104
116
}
105
117
try {
106
- return instantiateClass (clazz .getDeclaredConstructor ());
118
+ Constructor <T > ctor = (kotlinPresent && isKotlinClass (clazz ) ?
119
+ KotlinDelegate .findPrimaryConstructor (clazz ) : clazz .getDeclaredConstructor ());
120
+ if (ctor == null ) {
121
+ throw new BeanInstantiationException (clazz , "No default constructor found" );
122
+ }
123
+ return instantiateClass (ctor );
107
124
}
108
125
catch (NoSuchMethodException ex ) {
109
126
throw new BeanInstantiationException (clazz , "No default constructor found" , ex );
@@ -132,9 +149,11 @@ public static <T> T instantiateClass(Class<?> clazz, Class<T> assignableTo) thro
132
149
/**
133
150
* Convenience method to instantiate a class using the given constructor.
134
151
* <p>Note that this method tries to set the constructor accessible if given a
135
- * non-accessible (that is, non-public) constructor.
152
+ * non-accessible (that is, non-public) constructor, and supports Kotlin classes
153
+ * with optional parameters and default values.
136
154
* @param ctor the constructor to instantiate
137
- * @param args the constructor arguments to apply
155
+ * @param args the constructor arguments to apply (use null for unspecified parameter
156
+ * if needed for Kotlin classes with optional parameters and default values)
138
157
* @return the new instance
139
158
* @throws BeanInstantiationException if the bean cannot be instantiated
140
159
* @see Constructor#newInstance
@@ -143,7 +162,7 @@ public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws
143
162
Assert .notNull (ctor , "Constructor must not be null" );
144
163
try {
145
164
ReflectionUtils .makeAccessible (ctor );
146
- return ctor .newInstance (args );
165
+ return ( kotlinPresent && isKotlinClass ( ctor .getDeclaringClass ()) ? KotlinDelegate . instantiateClass ( ctor , args ) : ctor . newInstance (args ) );
147
166
}
148
167
catch (InstantiationException ex ) {
149
168
throw new BeanInstantiationException (ctor , "Is it an abstract class?" , ex );
@@ -299,6 +318,37 @@ else if (!method.isBridge() && targetMethod.getParameterCount() == numParams) {
299
318
return targetMethod ;
300
319
}
301
320
321
+ /**
322
+ * Return the primary constructor of the provided class (single or default constructor
323
+ * for Java classes and primary constructor for Kotlin classes) if any.
324
+ * @param clazz the {@link Class} of the Kotlin class
325
+ * @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">http://kotlinlang.org/docs/reference/classes.html#constructors</a>
326
+ * @since 5.0
327
+ */
328
+ @ SuppressWarnings ("unchecked" )
329
+ @ Nullable
330
+ public static <T > Constructor <T > findPrimaryConstructor (Class <T > clazz ) {
331
+ Assert .notNull (clazz , "Class must not be null" );
332
+ Constructor <T > ctor = null ;
333
+ if (kotlinPresent && isKotlinClass (clazz )) {
334
+ ctor = KotlinDelegate .findPrimaryConstructor (clazz );
335
+ }
336
+ else {
337
+ Constructor <T >[] ctors = (Constructor <T >[])clazz .getConstructors ();
338
+ if (ctors .length == 1 ) {
339
+ ctor = ctors [0 ];
340
+ }
341
+ else {
342
+ try {
343
+ ctor = clazz .getDeclaredConstructor ();
344
+ }
345
+ catch (NoSuchMethodException e ) {
346
+ }
347
+ }
348
+ }
349
+ return ctor ;
350
+ }
351
+
302
352
/**
303
353
* Parse a method signature in the form {@code methodName[([arg_list])]},
304
354
* where {@code arg_list} is an optional, comma-separated list of fully-qualified
@@ -646,4 +696,63 @@ private static void copyProperties(Object source, Object target, @Nullable Class
646
696
}
647
697
}
648
698
699
+ /**
700
+ * Return true if the specified class is a Kotlin one.
701
+ */
702
+ private static boolean isKotlinClass (Class <?> clazz ) {
703
+ for (Annotation annotation : clazz .getDeclaredAnnotations ()) {
704
+ if (annotation .annotationType ().getName ().equals ("kotlin.Metadata" )) {
705
+ return true ;
706
+ }
707
+ }
708
+ return false ;
709
+ }
710
+
711
+
712
+ /**
713
+ * Inner class to avoid a hard dependency on Kotlin at runtime.
714
+ */
715
+ private static class KotlinDelegate {
716
+
717
+ /**
718
+ * Return the Java constructor corresponding to the Kotlin primary constructor if any.
719
+ * @param clazz the {@link Class} of the Kotlin class
720
+ * @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">http://kotlinlang.org/docs/reference/classes.html#constructors</a>
721
+ */
722
+ @ Nullable
723
+ public static <T > Constructor <T > findPrimaryConstructor (Class <T > clazz ) {
724
+ KFunction <T > primaryConstructor = KClasses .getPrimaryConstructor (JvmClassMappingKt .getKotlinClass (clazz ));
725
+ if (primaryConstructor == null ) {
726
+ return null ;
727
+ }
728
+ Constructor <T > constructor = ReflectJvmMapping .getJavaConstructor (primaryConstructor );
729
+ Assert .notNull (constructor , "Can't get the Java constructor corresponding to the Kotlin primary constructor of " + clazz .getName ());
730
+ return constructor ;
731
+ }
732
+
733
+ /**
734
+ * Instantiate a Kotlin class using the provided constructor.
735
+ * @param ctor the constructor of the Kotlin class to instantiate
736
+ * @param args the constructor arguments to apply (use null for unspecified parameter if needed)
737
+ * @throws BeanInstantiationException if no primary constructor can be found
738
+ */
739
+ public static <T > T instantiateClass (Constructor <T > ctor , Object ... args ) {
740
+ KFunction <T > kotlinConstructor = ReflectJvmMapping .getKotlinFunction (ctor );
741
+ if (kotlinConstructor == null ) {
742
+ throw new BeanInstantiationException (ctor .getDeclaringClass (), "No corresponding Kotlin constructor found" );
743
+ }
744
+ List <KParameter > parameters = kotlinConstructor .getParameters ();
745
+ Map <KParameter , Object > argParameters = new HashMap <>(parameters .size ());
746
+ Assert .isTrue (args .length <= parameters .size (),
747
+ "The number of provided arguments should be less of equals than the number of constructor parameters" );
748
+ for (int i = 0 ; i < args .length ; i ++) {
749
+ if (!(parameters .get (i ).isOptional () && (args [i ] == null ))) {
750
+ argParameters .put (parameters .get (i ), args [i ]);
751
+ }
752
+ }
753
+ return kotlinConstructor .callBy (argParameters );
754
+ }
755
+
756
+ }
757
+
649
758
}
0 commit comments