Skip to content

Commit fdafe96

Browse files
mp911deodrotbohm
authored andcommitted
DATACMNS-1200 - Fix entity instantiation of Kotlin types using primitives with default values.
We now determine initial values for primitive parameters in Kotlin constructors that are absent (null) and defaulted. We default all Java primitive types to their initial zero value to prevent possible NullPointerExceptions. Kotlin defaulting uses a bitmask to determine which parameter should be defaulted but still requires the appropriate type. Previously, null values were attempted to cast/unbox and caused NullPointerException even though they had default values through Kotlin assigned. Original pull request: #255.
1 parent c6d8803 commit fdafe96

File tree

2 files changed

+69
-4
lines changed

2 files changed

+69
-4
lines changed

src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,15 +229,19 @@ private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments
229229
int slot = i / 32;
230230
int offset = slot * 32;
231231

232-
Object param = provider.getParameterValue(parameters.get(i));
232+
Parameter<Object, P> parameter = parameters.get(i);
233+
Class<Object> type = parameter.getType().getType();
234+
Object param = provider.getParameterValue(parameter);
233235

234236
KParameter kParameter = kParameters.get(i);
235237

236238
// what about null and parameter is mandatory? What if parameter is non-null?
237-
if (kParameter.isOptional()) {
239+
if (kParameter.isOptional() && param == null) {
238240

239-
if (param == null) {
240-
defaulting[slot] = defaulting[slot] | (1 << (i - offset));
241+
defaulting[slot] = defaulting[slot] | (1 << (i - offset));
242+
243+
if (type.isPrimitive()) {
244+
param = getPrimitiveDefault(type);
241245
}
242246
}
243247

@@ -251,5 +255,42 @@ private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments
251255

252256
return params;
253257
}
258+
259+
private static Object getPrimitiveDefault(Class<?> type) {
260+
261+
if (type == Byte.TYPE || type == Byte.class) {
262+
return (byte) 0;
263+
}
264+
265+
if (type == Short.TYPE || type == Short.class) {
266+
return (short) 0;
267+
}
268+
269+
if (type == Integer.TYPE || type == Integer.class) {
270+
return 0;
271+
}
272+
273+
if (type == Long.TYPE || type == Long.class) {
274+
return 0L;
275+
}
276+
277+
if (type == Float.TYPE || type == Float.class) {
278+
return 0F;
279+
}
280+
281+
if (type == Double.TYPE || type == Double.class) {
282+
return 0D;
283+
}
284+
285+
if (type == Character.TYPE || type == Character.class) {
286+
return '\u0000';
287+
}
288+
289+
if (type == Boolean.TYPE) {
290+
return Boolean.FALSE;
291+
}
292+
293+
throw new IllegalArgumentException(String.format("Primitive type %s not supported!", type));
294+
}
254295
}
255296
}

src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,27 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
9696
.hasCauseInstanceOf(IllegalArgumentException::class.java)
9797
}
9898

99+
@Test // DATACMNS-1200
100+
fun `should apply primitive defaulting for absent parameters`() {
101+
102+
val entity = this.entity as PersistentEntity<WithPrimitiveDefaulting, SamplePersistentProperty>
103+
val constructor = PreferredConstructorDiscoverer.discover<WithPrimitiveDefaulting, SamplePersistentProperty>(WithPrimitiveDefaulting::class.java)
104+
105+
doReturn(constructor).whenever(entity).persistenceConstructor
106+
doReturn(constructor.constructor.declaringClass).whenever(entity).type
107+
108+
val instance: WithPrimitiveDefaulting = KotlinClassGeneratingEntityInstantiator().createInstance(entity, provider)
109+
110+
Assertions.assertThat(instance.aByte).isEqualTo(0)
111+
Assertions.assertThat(instance.aShort).isEqualTo(0)
112+
Assertions.assertThat(instance.anInt).isEqualTo(0)
113+
Assertions.assertThat(instance.aLong).isEqualTo(0L)
114+
Assertions.assertThat(instance.aFloat).isEqualTo(0.0f)
115+
Assertions.assertThat(instance.aDouble).isEqualTo(0.0)
116+
Assertions.assertThat(instance.aChar).isEqualTo('a')
117+
Assertions.assertThat(instance.aBool).isTrue()
118+
}
119+
99120
data class Contact(val firstname: String, val lastname: String)
100121

101122
data class ContactWithDefaulting(val prop0: String, val prop1: String = "White", val prop2: String,
@@ -113,5 +134,8 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests {
113134
)
114135

115136
data class WithBoolean(val state: Boolean)
137+
138+
data class WithPrimitiveDefaulting(val aByte: Byte = 0, val aShort: Short = 0, val anInt: Int = 0, val aLong: Long = 0L,
139+
val aFloat: Float = 0.0f, val aDouble: Double = 0.0, val aChar: Char = 'a', val aBool: Boolean = true)
116140
}
117141

0 commit comments

Comments
 (0)