From cf5af6a971a0abf4b85e180463f1c389ecc57ba1 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 18 Oct 2017 15:12:38 +0200 Subject: [PATCH] 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. --- ...tlinClassGeneratingEntityInstantiator.java | 49 +++++++++++++++++-- ...ssGeneratingEntityInstantiatorUnitTests.kt | 24 +++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java index 05b7d5cd3..9b35c17e1 100644 --- a/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java +++ b/src/main/java/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiator.java @@ -229,15 +229,19 @@ public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEnti int slot = i / 32; int offset = slot * 32; - Object param = provider.getParameterValue(parameters.get(i)); + Parameter parameter = parameters.get(i); + Class type = parameter.getType().getType(); + Object param = provider.getParameterValue(parameter); KParameter kParameter = kParameters.get(i); // what about null and parameter is mandatory? What if parameter is non-null? - if (kParameter.isOptional()) { + if (kParameter.isOptional() && param == null) { - if (param == null) { - defaulting[slot] = defaulting[slot] | (1 << (i - offset)); + defaulting[slot] = defaulting[slot] | (1 << (i - offset)); + + if (type.isPrimitive()) { + param = getPrimitiveDefault(type); } } @@ -251,5 +255,42 @@ public class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEnti return params; } + + private static Object getPrimitiveDefault(Class type) { + + if (type == Byte.TYPE || type == Byte.class) { + return (byte) 0; + } + + if (type == Short.TYPE || type == Short.class) { + return (short) 0; + } + + if (type == Integer.TYPE || type == Integer.class) { + return 0; + } + + if (type == Long.TYPE || type == Long.class) { + return 0L; + } + + if (type == Float.TYPE || type == Float.class) { + return 0F; + } + + if (type == Double.TYPE || type == Double.class) { + return 0D; + } + + if (type == Character.TYPE || type == Character.class) { + return '\u0000'; + } + + if (type == Boolean.TYPE) { + return Boolean.FALSE; + } + + throw new IllegalArgumentException(String.format("Primitive type %s not supported!", type)); + } } } diff --git a/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt b/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt index a2d05cb66..14e0da6b4 100644 --- a/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt @@ -96,6 +96,27 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests { .hasCauseInstanceOf(IllegalArgumentException::class.java) } + @Test // DATACMNS-1200 + fun `should apply primitive defaulting for absent parameters`() { + + val entity = this.entity as PersistentEntity + val constructor = PreferredConstructorDiscoverer.discover(WithPrimitiveDefaulting::class.java) + + doReturn(constructor).whenever(entity).persistenceConstructor + doReturn(constructor.constructor.declaringClass).whenever(entity).type + + val instance: WithPrimitiveDefaulting = KotlinClassGeneratingEntityInstantiator().createInstance(entity, provider) + + Assertions.assertThat(instance.aByte).isEqualTo(0) + Assertions.assertThat(instance.aShort).isEqualTo(0) + Assertions.assertThat(instance.anInt).isEqualTo(0) + Assertions.assertThat(instance.aLong).isEqualTo(0L) + Assertions.assertThat(instance.aFloat).isEqualTo(0.0f) + Assertions.assertThat(instance.aDouble).isEqualTo(0.0) + Assertions.assertThat(instance.aChar).isEqualTo('a') + Assertions.assertThat(instance.aBool).isTrue() + } + data class Contact(val firstname: String, val lastname: String) data class ContactWithDefaulting(val prop0: String, val prop1: String = "White", val prop2: String, @@ -113,5 +134,8 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests { ) data class WithBoolean(val state: Boolean) + + data class WithPrimitiveDefaulting(val aByte: Byte = 0, val aShort: Short = 0, val anInt: Int = 0, val aLong: Long = 0L, + val aFloat: Float = 0.0f, val aDouble: Double = 0.0, val aChar: Char = 'a', val aBool: Boolean = true) }