Infer Kotlin null-safety from type variables

This commit removes the constraint from type variables in
PropertyResolver, JdbcOperations and RestOperations
Kotlin extensions in order to get null-safety inferred
from the type declared by the user.

Closes gh-22687
This commit is contained in:
Sebastien Deleuze
2019-03-27 08:50:42 +01:00
parent 68a529b915
commit cbb5a78aa0
6 changed files with 175 additions and 124 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,8 +34,8 @@ import java.net.URI
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.getForObject(url: String, vararg uriVariables: Any): T? =
getForObject(url, T::class.java, *uriVariables)
inline fun <reified T> RestOperations.getForObject(url: String, vararg uriVariables: Any): T =
getForObject(url, T::class.java, *uriVariables) as T
/**
* Extension for [RestOperations.getForObject] providing a `getForObject<Foo>(...)`
@@ -48,8 +48,8 @@ inline fun <reified T: Any> RestOperations.getForObject(url: String, vararg uriV
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.getForObject(url: String, uriVariables: Map<String, Any?>): T? =
getForObject(url, T::class.java, uriVariables)
inline fun <reified T> RestOperations.getForObject(url: String, uriVariables: Map<String, Any?>): T =
getForObject(url, T::class.java, uriVariables) as T
/**
* Extension for [RestOperations.getForObject] providing a `getForObject<Foo>(...)`
@@ -62,8 +62,8 @@ inline fun <reified T: Any> RestOperations.getForObject(url: String, uriVariable
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.getForObject(url: URI): T? =
getForObject(url, T::class.java)
inline fun <reified T> RestOperations.getForObject(url: URI): T =
getForObject(url, T::class.java) as T
/**
* Extension for [RestOperations.getForEntity] providing a `getForEntity<Foo>(...)`
@@ -75,7 +75,7 @@ inline fun <reified T: Any> RestOperations.getForObject(url: URI): T? =
* @since 5.0.2
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.getForEntity(url: URI): ResponseEntity<T> =
inline fun <reified T> RestOperations.getForEntity(url: URI): ResponseEntity<T> =
getForEntity(url, T::class.java)
/**
@@ -89,7 +89,7 @@ inline fun <reified T: Any> RestOperations.getForEntity(url: URI): ResponseEntit
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.getForEntity(url: String, vararg uriVariables: Any): ResponseEntity<T> =
inline fun <reified T> RestOperations.getForEntity(url: String, vararg uriVariables: Any): ResponseEntity<T> =
getForEntity(url, T::class.java, *uriVariables)
/**
@@ -102,7 +102,7 @@ inline fun <reified T: Any> RestOperations.getForEntity(url: String, vararg uriV
* @since 5.0.2
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.getForEntity(url: String, uriVariables: Map<String, *>): ResponseEntity<T> =
inline fun <reified T> RestOperations.getForEntity(url: String, uriVariables: Map<String, *>): ResponseEntity<T> =
getForEntity(url, T::class.java, uriVariables)
/**
@@ -115,9 +115,9 @@ inline fun <reified T: Any> RestOperations.getForEntity(url: String, uriVariable
* @since 5.0.2
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.patchForObject(url: String, request: Any? = null,
vararg uriVariables: Any): T? =
patchForObject(url, request, T::class.java, *uriVariables)
inline fun <reified T> RestOperations.patchForObject(url: String, request: Any? = null,
vararg uriVariables: Any): T =
patchForObject(url, request, T::class.java, *uriVariables) as T
/**
* Extension for [RestOperations.patchForObject] providing a `patchForObject<Foo>(...)`
@@ -129,9 +129,9 @@ inline fun <reified T: Any> RestOperations.patchForObject(url: String, request:
* @since 5.0.2
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.patchForObject(url: String, request: Any? = null,
uriVariables: Map<String, *>): T? =
patchForObject(url, request, T::class.java, uriVariables)
inline fun <reified T> RestOperations.patchForObject(url: String, request: Any? = null,
uriVariables: Map<String, *>): T =
patchForObject(url, request, T::class.java, uriVariables) as T
/**
* Extension for [RestOperations.patchForObject] providing a `patchForObject<Foo>(...)`
@@ -143,8 +143,8 @@ inline fun <reified T: Any> RestOperations.patchForObject(url: String, request:
* @since 5.0.2
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.patchForObject(url: URI, request: Any? = null): T? =
patchForObject(url, request, T::class.java)
inline fun <reified T> RestOperations.patchForObject(url: URI, request: Any? = null): T =
patchForObject(url, request, T::class.java) as T
/**
* Extension for [RestOperations.postForObject] providing a `postForObject<Foo>(...)`
@@ -157,9 +157,9 @@ inline fun <reified T: Any> RestOperations.patchForObject(url: URI, request: Any
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.postForObject(url: String, request: Any? = null,
vararg uriVariables: Any): T? =
postForObject(url, request, T::class.java, *uriVariables)
inline fun <reified T> RestOperations.postForObject(url: String, request: Any? = null,
vararg uriVariables: Any): T =
postForObject(url, request, T::class.java, *uriVariables) as T
/**
* Extension for [RestOperations.postForObject] providing a `postForObject<Foo>(...)`
@@ -172,9 +172,9 @@ inline fun <reified T: Any> RestOperations.postForObject(url: String, request: A
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.postForObject(url: String, request: Any? = null,
uriVariables: Map<String, *>): T? =
postForObject(url, request, T::class.java, uriVariables)
inline fun <reified T> RestOperations.postForObject(url: String, request: Any? = null,
uriVariables: Map<String, *>): T =
postForObject(url, request, T::class.java, uriVariables) as T
/**
* Extension for [RestOperations.postForObject] providing a `postForObject<Foo>(...)`
@@ -187,8 +187,8 @@ inline fun <reified T: Any> RestOperations.postForObject(url: String, request: A
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.postForObject(url: URI, request: Any? = null): T? =
postForObject(url, request, T::class.java)
inline fun <reified T> RestOperations.postForObject(url: URI, request: Any? = null): T =
postForObject(url, request, T::class.java) as T
/**
* Extension for [RestOperations.postForEntity] providing a `postForEntity<Foo>(...)`
@@ -201,7 +201,7 @@ inline fun <reified T: Any> RestOperations.postForObject(url: URI, request: Any?
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.postForEntity(url: String, request: Any? = null,
inline fun <reified T> RestOperations.postForEntity(url: String, request: Any? = null,
vararg uriVariables: Any): ResponseEntity<T> =
postForEntity(url, request, T::class.java, *uriVariables)
@@ -216,7 +216,7 @@ inline fun <reified T: Any> RestOperations.postForEntity(url: String, request: A
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.postForEntity(url: String, request: Any? = null,
inline fun <reified T> RestOperations.postForEntity(url: String, request: Any? = null,
uriVariables: Map<String, *>): ResponseEntity<T> =
postForEntity(url, request, T::class.java, uriVariables)
@@ -231,7 +231,7 @@ inline fun <reified T: Any> RestOperations.postForEntity(url: String, request: A
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.postForEntity(url: URI, request: Any? = null): ResponseEntity<T> =
inline fun <reified T> RestOperations.postForEntity(url: URI, request: Any? = null): ResponseEntity<T> =
postForEntity(url, request, T::class.java)
/**
@@ -244,7 +244,7 @@ inline fun <reified T: Any> RestOperations.postForEntity(url: URI, request: Any?
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.exchange(url: String, method: HttpMethod,
inline fun <reified T> RestOperations.exchange(url: String, method: HttpMethod,
requestEntity: HttpEntity<*>? = null, vararg uriVariables: Any): ResponseEntity<T> =
exchange(url, method, requestEntity, object : ParameterizedTypeReference<T>() {}, *uriVariables)
@@ -258,7 +258,7 @@ inline fun <reified T: Any> RestOperations.exchange(url: String, method: HttpMet
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.exchange(url: String, method: HttpMethod,
inline fun <reified T> RestOperations.exchange(url: String, method: HttpMethod,
requestEntity: HttpEntity<*>? = null, uriVariables: Map<String, *>): ResponseEntity<T> =
exchange(url, method, requestEntity, object : ParameterizedTypeReference<T>() {}, uriVariables)
@@ -272,7 +272,7 @@ inline fun <reified T: Any> RestOperations.exchange(url: String, method: HttpMet
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.exchange(url: URI, method: HttpMethod,
inline fun <reified T> RestOperations.exchange(url: URI, method: HttpMethod,
requestEntity: HttpEntity<*>? = null): ResponseEntity<T> =
exchange(url, method, requestEntity, object : ParameterizedTypeReference<T>() {})
@@ -286,5 +286,5 @@ inline fun <reified T: Any> RestOperations.exchange(url: URI, method: HttpMethod
* @since 5.0
*/
@Throws(RestClientException::class)
inline fun <reified T: Any> RestOperations.exchange(requestEntity: RequestEntity<*>): ResponseEntity<T> =
inline fun <reified T> RestOperations.exchange(requestEntity: RequestEntity<*>): ResponseEntity<T> =
exchange(requestEntity, object : ParameterizedTypeReference<T>() {})

View File

@@ -16,14 +16,14 @@
package org.springframework.web.client
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Test
import org.springframework.core.ParameterizedTypeReference
import org.springframework.http.HttpEntity
import org.springframework.http.HttpMethod
import org.springframework.http.RequestEntity
import org.springframework.http.*
import org.springframework.util.ReflectionUtils
import java.net.URI
import kotlin.reflect.full.createType
@@ -36,14 +36,19 @@ import kotlin.reflect.jvm.kotlinFunction
*/
class RestOperationsExtensionsTests {
val template = mockk<RestOperations>(relaxed = true)
val template = mockk<RestOperations>()
val foo = mockk<Foo>()
val entity = mockk<ResponseEntity<Foo>>()
@Test
fun `getForObject with reified type parameters, String and varargs`() {
val url = "https://spring.io"
val var1 = "var1"
val var2 = "var2"
template.getForObject<Foo>(url, var1, var2)
every { template.getForObject(url, Foo::class.java, var1, var2) } returns foo
assertEquals(foo, template.getForObject<Foo>(url, var1, var2))
verify { template.getForObject(url, Foo::class.java, var1, var2) }
}
@@ -51,21 +56,24 @@ class RestOperationsExtensionsTests {
fun `getForObject with reified type parameters, String and Map`() {
val url = "https://spring.io"
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.getForObject<Foo>(url, vars)
every { template.getForObject(url, Foo::class.java, vars) } returns foo
assertEquals(foo, template.getForObject<Foo>(url, vars))
verify { template.getForObject(url, Foo::class.java, vars) }
}
@Test
fun `getForObject with reified type parameters and URI`() {
val url = URI("https://spring.io")
template.getForObject<Foo>(url)
every { template.getForObject(url, Foo::class.java) } returns foo
assertEquals(foo, template.getForObject<Foo>(url))
verify { template.getForObject(url, Foo::class.java) }
}
@Test
fun `getForEntity with reified type parameters, String and URI`() {
val url = URI("https://spring.io")
template.getForEntity<Foo>(url)
every { template.getForEntity(url, Foo::class.java) } returns entity
assertEquals(entity, template.getForEntity<Foo>(url))
verify { template.getForEntity(url, Foo::class.java) }
}
@@ -74,7 +82,8 @@ class RestOperationsExtensionsTests {
val url = "https://spring.io"
val var1 = "var1"
val var2 = "var2"
template.getForEntity<Foo>(url, var1, var2)
every { template.getForEntity(url, Foo::class.java, var1, var2) } returns entity
assertEquals(entity, template.getForEntity<Foo>(url, var1, var2))
verify { template.getForEntity(url, Foo::class.java, var1, var2) }
}
@@ -82,7 +91,8 @@ class RestOperationsExtensionsTests {
fun `getForEntity with reified type parameters and Map`() {
val url = "https://spring.io"
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.getForEntity<Foo>(url, vars)
every { template.getForEntity(url, Foo::class.java, vars) } returns entity
assertEquals(entity, template.getForEntity<Foo>(url, vars))
verify { template.getForEntity(url, Foo::class.java, vars) }
}
@@ -92,7 +102,8 @@ class RestOperationsExtensionsTests {
val body: Any = "body"
val var1 = "var1"
val var2 = "var2"
template.patchForObject<Foo>(url, body, var1, var2)
every { template.patchForObject(url, body, Foo::class.java, var1, var2) } returns foo
assertEquals(foo, template.patchForObject<Foo>(url, body, var1, var2))
verify { template.patchForObject(url, body, Foo::class.java, var1, var2) }
}
@@ -101,7 +112,8 @@ class RestOperationsExtensionsTests {
val url = "https://spring.io"
val body: Any = "body"
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.patchForObject<Foo>(url, body, vars)
every { template.patchForObject(url, body, Foo::class.java, vars) } returns foo
assertEquals(foo, template.patchForObject<Foo>(url, body, vars))
verify { template.patchForObject(url, body, Foo::class.java, vars) }
}
@@ -109,14 +121,16 @@ class RestOperationsExtensionsTests {
fun `patchForObject with reified type parameters and String`() {
val url = "https://spring.io"
val body: Any = "body"
template.patchForObject<Foo>(url, body)
every { template.patchForObject(url, body, Foo::class.java) } returns foo
assertEquals(foo, template.patchForObject<Foo>(url, body))
verify { template.patchForObject(url, body, Foo::class.java) }
}
@Test
fun `patchForObject with reified type parameters`() {
val url = "https://spring.io"
template.patchForObject<Foo>(url)
every { template.patchForObject(url, null, Foo::class.java) } returns foo
assertEquals(foo, template.patchForObject<Foo>(url))
verify { template.patchForObject(url, null, Foo::class.java) }
}
@@ -126,7 +140,8 @@ class RestOperationsExtensionsTests {
val body: Any = "body"
val var1 = "var1"
val var2 = "var2"
template.postForObject<Foo>(url, body, var1, var2)
every { template.postForObject(url, body, Foo::class.java, var1, var2) } returns foo
assertEquals(foo, template.postForObject<Foo>(url, body, var1, var2))
verify { template.postForObject(url, body, Foo::class.java, var1, var2) }
}
@@ -135,7 +150,8 @@ class RestOperationsExtensionsTests {
val url = "https://spring.io"
val body: Any = "body"
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.postForObject<Foo>(url, body, vars)
every { template.postForObject(url, body, Foo::class.java, vars) } returns foo
assertEquals(foo, template.postForObject<Foo>(url, body, vars))
verify { template.postForObject(url, body, Foo::class.java, vars) }
}
@@ -143,14 +159,16 @@ class RestOperationsExtensionsTests {
fun `postForObject with reified type parameters and String`() {
val url = "https://spring.io"
val body: Any = "body"
template.postForObject<Foo>(url, body)
every { template.postForObject(url, body, Foo::class.java) } returns foo
assertEquals(foo, template.postForObject<Foo>(url, body))
verify { template.postForObject(url, body, Foo::class.java) }
}
@Test
fun `postForObject with reified type parameters`() {
val url = "https://spring.io"
template.postForObject<Foo>(url)
every { template.postForObject(url, null, Foo::class.java) } returns foo
assertEquals(foo, template.postForObject<Foo>(url))
verify { template.postForObject(url, null, Foo::class.java) }
}
@@ -160,7 +178,8 @@ class RestOperationsExtensionsTests {
val body: Any = "body"
val var1 = "var1"
val var2 = "var2"
template.postForEntity<Foo>(url, body, var1, var2)
every { template.postForEntity(url, body, Foo::class.java, var1, var2) } returns entity
assertEquals(entity, template.postForEntity<Foo>(url, body, var1, var2))
verify { template.postForEntity(url, body, Foo::class.java, var1, var2) }
}
@@ -169,7 +188,8 @@ class RestOperationsExtensionsTests {
val url = "https://spring.io"
val body: Any = "body"
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.postForEntity<Foo>(url, body, vars)
every { template.postForEntity(url, body, Foo::class.java, vars) } returns entity
assertEquals(entity, template.postForEntity<Foo>(url, body, vars))
verify { template.postForEntity(url, body, Foo::class.java, vars) }
}
@@ -177,27 +197,30 @@ class RestOperationsExtensionsTests {
fun `postForEntity with reified type parameters and String`() {
val url = "https://spring.io"
val body: Any = "body"
template.postForEntity<Foo>(url, body)
every { template.postForEntity(url, body, Foo::class.java) } returns entity
assertEquals(entity, template.postForEntity<Foo>(url, body))
verify { template.postForEntity(url, body, Foo::class.java) }
}
@Test
fun `postForEntity with reified type parameters`() {
val url = "https://spring.io"
template.postForEntity<Foo>(url)
verify { template.postForEntity(url, null, Foo::class.java) }
every { template.postForEntity(url, null, Foo::class.java) } returns entity
assertEquals(entity, template.postForEntity<Foo>(url))
verify { template.postForEntity(url, null, Foo::class.java) }
}
@Test
fun `exchange with reified type parameters, String, HttpMethod, HttpEntity and varargs`() {
val url = "https://spring.io"
val method = HttpMethod.GET
val entity = mockk<HttpEntity<Foo>>()
val var1 = "var1"
val var2 = "var2"
template.exchange<List<Foo>>(url, method, entity, var1, var2)
verify { template.exchange(url, method, entity,
object : ParameterizedTypeReference<List<Foo>>() {}, var1, var2) }
val entityList = mockk<ResponseEntity<List<Foo>>>()
val responseType = object : ParameterizedTypeReference<List<Foo>>() {}
every { template.exchange(url, method, entity, responseType, var1, var2) } returns entityList
assertEquals(entityList, template.exchange<List<Foo>>(url, method, entity, var1, var2))
verify { template.exchange(url, method, entity, responseType, var1, var2) }
}
@Test
@@ -206,9 +229,11 @@ class RestOperationsExtensionsTests {
val method = HttpMethod.GET
val entity = mockk<HttpEntity<Foo>>()
val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
template.exchange<List<Foo>>(url, method, entity, vars)
verify { template.exchange(url, method, entity,
object : ParameterizedTypeReference<List<Foo>>() {}, vars) }
val entityList = mockk<ResponseEntity<List<Foo>>>()
val responseType = object : ParameterizedTypeReference<List<Foo>>() {}
every { template.exchange(url, method, entity, responseType, vars) } returns entityList
assertEquals(entityList, template.exchange<List<Foo>>(url, method, entity, vars))
verify { template.exchange(url, method, entity, responseType, vars) }
}
@Test
@@ -216,26 +241,32 @@ class RestOperationsExtensionsTests {
val url = "https://spring.io"
val method = HttpMethod.GET
val entity = mockk<HttpEntity<Foo>>()
template.exchange<List<Foo>>(url, method, entity)
verify { template.exchange(url, method, entity,
object : ParameterizedTypeReference<List<Foo>>() {}) }
val entityList = mockk<ResponseEntity<List<Foo>>>()
val responseType = object : ParameterizedTypeReference<List<Foo>>() {}
every { template.exchange(url, method, entity, responseType) } returns entityList
assertEquals(entityList, template.exchange<List<Foo>>(url, method, entity))
verify { template.exchange(url, method, entity, responseType) }
}
@Test
fun `exchange with reified type parameters, String and HttpMethod`() {
val url = "https://spring.io"
val method = HttpMethod.GET
template.exchange<List<Foo>>(url, method)
verify { template.exchange(url, method, null,
object : ParameterizedTypeReference<List<Foo>>() {}) }
val entityList = mockk<ResponseEntity<List<Foo>>>()
val responseType = object : ParameterizedTypeReference<List<Foo>>() {}
every { template.exchange(url, method, null, responseType) } returns entityList
assertEquals(entityList, template.exchange<List<Foo>>(url, method))
verify { template.exchange(url, method, null, responseType) }
}
@Test
fun `exchange with reified type parameters, String and HttpEntity`() {
val entity = mockk<RequestEntity<Foo>>()
template.exchange<List<Foo>>(entity)
verify { template.exchange(entity,
object : ParameterizedTypeReference<List<Foo>>() {}) }
val entityList = mockk<ResponseEntity<List<Foo>>>()
val responseType = object : ParameterizedTypeReference<List<Foo>>() {}
every { template.exchange(entity, responseType) } returns entityList
assertEquals(entityList, template.exchange<List<Foo>>(entity))
verify { template.exchange(entity, responseType) }
}
@Test
@@ -247,7 +278,8 @@ class RestOperationsExtensionsTests {
val parameters = mutableListOf<Class<*>>(RestOperations::class.java).apply { addAll(method.parameterTypes.filter { it != kClass.java }) }
val f = extensions.getDeclaredMethod(method.name, *parameters.toTypedArray()).kotlinFunction!!
Assert.assertEquals(1, f.typeParameters.size)
Assert.assertEquals(listOf(Any::class.createType()), f.typeParameters[0].upperBounds)
System.out.println(method.name + f.typeParameters)
Assert.assertEquals("Failed: " + method.name, listOf(Any::class.createType(nullable = true)), f.typeParameters[0].upperBounds)
}
}
}