How are generics managed in Kotlin?:
I have an interface and its corresponding implementation generated with IntelliJ from java code. The interface that has generated me is:
package com.oesia.mako.commons.repository
import com.oesia.mako.commons.dto.Page
/**
* Plantilla genérica para los DAO. Para no repetir los DAO's utilizamos
* generics de java. Ya que todas los DAO's a priori hacen las mismas
* operaciones.
* @param T Clase de los objetos a persistir/manipular en BD.
* *
* @param PK Tipo de la primary key.
* *
* @param C Criteria para montar la cláusula where de las queries.
* *
* @param Q Tipo Query para un los objetos clase T.
* *
* *
* @author José-Alberto Gilberte León ([email protected]).
*/
interface IRepository<T, PK, C, Q> {
/**
* Devuelve todos los objetos de tipo T que cumplan los criterios y el filtro si los tienen
* @param namedQuery Nombre de la query customizada que se desea ejecutar.
* *
* @param criteria Dto con criterios de busqueda
* *
* @return List con todos los objetos de tipo T que cumplen los criterios
*/
fun findByCriteria(namedQuery: String, criteria: C): List<T>
/**
* Devuelve todas los objetos de tipo T que cumplan los criterios y el filtro si los tienen
* en un objeto Page.
* @param namedQuery Query customizada que se desea ejecutar.
* *
* @param rowCountQuery Query de oonteo del número total de elementos
* *
* @param criteria Dto con criterios de búsqueda
* *
* @return Page con todas las entidades tipo T y contadores que cumplen el criterio.
*/
fun findPageByCriteria(namedQuery: String, rowCountQuery: String, criteria: C): Page<T>
/**
* Devuelve todos los objetos de tipo T que cumplan los criterios y el filtro si los tienen
* @param criteria Dto con criterios de busqueda
* *
* @return List con todos los objetos de tipo T que cumplen los criterios
*/
fun findByCriteria(criteria: C): List<T>
/**
* Devuelve todas los objetos de tipo T que cumplan los criterios y el filtro si los tienen
* en un objeto Page.
* @param criteria Dto con criterios de búsqueda
* *
* @return Page con todas las entidades tipo T y contadores que cumplen el criterio.
*/
fun findPageByCriteria(criteria: C): Page<T>
/**
* Obtiene un objeto T de la base de datos
* @param id El identificador de la entidad T
* *
* @return La entidad T
*/
fun findById(id: PK): T
/**
* Inserta un objeto T
* @param dto Un dto de la entidad T
* *
* @return La entidad T.
*/
fun insert(dto: T): T
/**
* Actualiza un objeto T
* @param dto Un dto de la entidad T
* *
* @return La entidad T.
*/
fun update(dto: T): T
/**
* Elimina un objeto T
* @param id del dto de tipo T
* *
* @return Número de filas borradas
*/
fun delete(id: PK): Int
/**
* Elimina n objetos T por Criteria.
* @param Criteria para borrar
* *
* @return Número de filas borradas
*/
fun deleteByCriteria(criteria: C): Int
}
And the implementation:
package com.oesia.mako.commons.repository.impl
import java.io.Serializable
import java.lang.reflect.ParameterizedType
import java.util.*
import org.slf4j.*
import org.springframework.dao.EmptyResultDataAccessException
import org.springframework.jdbc.core.RowMapper
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations
import org.springframework.jdbc.core.support.JdbcDaoSupport
import com.oesia.mako.commons.repository.IRepository
import com.oesia.mako.commons.dto.Page
import com.oesia.mako.commons.dto.criteria.BaseCriteria
import com.oesia.mako.commons.dto.model.SqlParametrizable
import com.oesia.mako.commons.jdbc.Query
/**
* Plantilla genérica para los DAO. Para no repetir los DAO's utilizamos
* generics de java. Ya que todas los DAO's a priori hacen las mismas
* operaciones.
* @param T - Clase de los objetos a persistir/manipular en BD.
* *
* @param PK - Tipo básico de la surrogate key
* *
* @param R - RowMapper mapeador de filas sql a objeto de tipo T
* *
* @param C - Criteria para montar la cláusula where de las queries.
* *
* @param Q - Query para montar el sql de las queries.
* *
* *
* @author José-Alberto Gilberte León ([email protected]).
*/
class Repository<T : SqlParametrizable<java.lang.String>, PK : Serializable, R : RowMapper<T>, C : BaseCriteria, Q : Query<C>> : JdbcDaoSupport(), IRepository<T, PK, C, Q> {
private val log = LoggerFactory.getLogger(javaClass)
private var rowMapperClass: Class<R>? = null
private var queryClass: Class<Q>? = null
var namedParameterJdbcOperations: NamedParameterJdbcOperations? = null
private val DEFAULT_PAGE_SIZE = 10
private val MINIMUN_PAGE_SIZE = 1
private val MINIMUN_PAGE_NUMBER = 1
private val SQL_WORD_LIMIT = " LIMIT "
private val SQL_WORD_OFFSET = " OFFSET "
private fun initializeRepository() {
var genericClass: Class<*> = javaClass
while (Repository<*, *, *, *, *>::class.java.simpleName != genericClass.superclass.simpleName) {
if (genericClass.genericSuperclass is ParameterizedType)
break
genericClass = genericClass.superclass
}
if (genericClass.genericSuperclass is ParameterizedType) {
val parameterizedType = genericClass.genericSuperclass as ParameterizedType
rowMapperClass = parameterizedType.actualTypeArguments[2] as Class<R>
queryClass = parameterizedType.actualTypeArguments[4] as Class<Q>
}
}
/**
* Constructor vacío
*/
init {
initializeRepository()
}
/**
* Muestra en las trazas de log los parámetros de la query.
* @param mapSqlParameterSource Parámetros de la consulta.
*/
private fun debugSqlParameters(mapSqlParameterSource: MapSqlParameterSource) {
if (isNotEmptyMapSqlParameterSource(mapSqlParameterSource)) {
var key: String? = null
var value: Any? = null
for ((key1, value1) in mapSqlParameterSource.values) {
key = key1
value = value1
log.debug(key + ": " + value)
}
}
}
private fun isNotEmptyMapSqlParameterSource(mapSqlParameterSource: MapSqlParameterSource?): Boolean {
if (mapSqlParameterSource != null
&& mapSqlParameterSource.values != null
&& mapSqlParameterSource.values.entries != null) {
return true
} else {
return false
}
}
/**
* Devuelve todos los objetos de tipo T que cumplan los criterios y el filtro si los tienen.
* @param criteria Dto con criterios de busqueda
* *
* @return List con todos los objetos de tipo T que cumplen los criterios
* *
* @throws Exception
*/
@Throws(Exception::class)
override fun findByCriteria(criteria: C): List<T> {
return findByCriteria(queryClass!!.newInstance().toSQLFetchRows(criteria), criteria)
}
/**
* Devuelve todos los objetos de tipo T que cumplan los criterios y el filtro si los tienen.
* @param namedQuery Query customizada que se desea ejecutar.
* *
* @param criteria Dto con criterios de busqueda
* *
* @return List con todos los objetos de tipo T que cumplen los criterios
* *
* @throws Exception
*/
@Throws(Exception::class)
override fun findByCriteria(namedQuery: String, criteria: C): List<T> {
return namedParameterJdbcOperations!!.query(namedQuery, criteria.mapSQLParameterSource, rowMapperClass!!.newInstance())
}
/**
* Devuelve todas los objetos de tipo T que cumplan los criterios y el filtro si los tienen
* en un objeto Page.
* @param criteria - Dto con criterios de búsqueda
* *
* @return Page con todas las entidades tipo T y contadores que cumplen el criterio.
* *
* @throws Exception
*/
@Throws(Exception::class)
override fun findPageByCriteria(criteria: C): Page<T> {
if (criteria.pageSize < MINIMUN_PAGE_SIZE)
criteria.pageSize = DEFAULT_PAGE_SIZE
if (criteria.actualPage < MINIMUN_PAGE_NUMBER)
criteria.actualPage = MINIMUN_PAGE_NUMBER
else if (criteria.totalPages > 0 && criteria.actualPage > criteria.totalPages)
criteria.actualPage = criteria.totalPages
val query = queryClass!!.newInstance()
return findPageByCriteria(query.toSQLFetchRows(criteria), query.toSQLRowCount(criteria), criteria)
}
/**
* Devuelve todas los objetos de tipo T que cumplan los criterios y el filtro si los tienen
* en un objeto Page.
* @param namedQuery Query customizada que se desea ejecutar.
* *
* @param rowCountQuery Query para contar el número total de filas.
* *
* @param criteria - Dto con criterios de búsqueda
* *
* @return Page con todas las entidades tipo T y contadores que cumplen el criterio.
* *
* @throws Exception
*/
@Throws(Exception::class)
override fun findPageByCriteria(namedQuery: String, rowCountQuery: String, criteria: C): Page<T> {
val sqlFetchRows = addLimitAndOffset(namedQuery, criteria)
val rowMapper = rowMapperClass!!.newInstance()
val res = namedParameterJdbcOperations!!.query(sqlFetchRows, criteria.mapSQLParameterSource, rowMapper)
val totalRows = namedParameterJdbcOperations!!.queryForObject(rowCountQuery, criteria.mapSQLParameterSource, Int::class.java)
criteria.totalPages = totalRows
val resultPage = Page<T>()
resultPage.content = res
return calculateCountersPage(resultPage, criteria, totalRows)
}
private fun addLimitAndOffset(query: String, criteria: C?): String {
val queryLimited = StringBuilder(query)
queryLimited.append(SQL_WORD_LIMIT)
queryLimited.append(criteria!!.pageSize)
queryLimited.append(SQL_WORD_OFFSET)
var offset = 0
if (criteria != null && criteria.actualPage > MINIMUN_PAGE_NUMBER)
offset = (criteria.actualPage - 1) * criteria.pageSize
return queryLimited.append(offset).toString()
}
private fun calculateCountersPage(page: Page<T>, criteria: C?, totalRows: Int): Page<T> {
page.totalElements = totalRows.toLong()
if (totalRows == 0)
page.totalPages = 0
else if (criteria != null && criteria.pageSize != 0)
page.totalPages = Math.ceil((totalRows / criteria.pageSize).toDouble()).toInt()
if (page.content != null && page.content.size > 0)
page.size = page.content.size
else
page.size = 0
page.number = criteria!!.actualPage
return page
}
/**
* Obtiene un objeto T de la base de datos
* @param id - El identificador de la entidad T
* *
* @return La entidad T
*/
override fun findById(id: PK): T {
val sql = queryClass!!.newInstance().toSQLFindById()
val keyNamePrimary = queryClass!!.newInstance().namePrimaryKey
val parameters = HashMap<String, Any>()
parameters.put(keyNamePrimary, id)
val rowMapper = rowMapperClass!!.newInstance()
var dto: T? = null
try {
dto = namedParameterJdbcOperations!!.queryForObject(sql, parameters, rowMapper)
} catch (e: EmptyResultDataAccessException) {
}
return dto
}
/**
* Inserta un objeto T
* @param dto - Un dto de la entidad T
* *
* @return La entidad T.
*/
override fun insert(dto: T): T {
namedParameterJdbcOperations!!.update(queryClass!!.newInstance().toSQLInsert(), dto.toMapSqlParameterSource())
return dto
}
/**
* Actualiza un objeto T
* @param dto - Un dto de la entidad T
* *
* @return La entidad T.
*/
override fun update(dto: T): T {
val parameters = dto.toMapSqlParameterSource()
val rows = namedParameterJdbcOperations!!.update(queryClass!!.newInstance().toSQLUpdate(), parameters)
if (rows != 0)
dto.version = dto.version!! + 1
return dto
}
/**
* Elimina un objeto T
* @param id - Identificador del dto de tipo T
* *
* @return Número de filas borradas
*/
override fun delete(id: PK): Int {
val parameters = HashMap<String, Any>()
parameters.put("ID", id)
return namedParameterJdbcOperations!!.update(queryClass!!.newInstance().toSQLDeleteById(), parameters)
}
/**
* Elimina n objetos T por Criteria.
* @param Criteria para borrar
* *
* @return Número de filas borradas
*/
override fun deleteByCriteria(criteria: C): Int {
return namedParameterJdbcOperations!!.update(queryClass!!.newInstance().toSQLDelete(criteria), criteria.mapSQLParameterSource.values)
}
}
The problem is that what is generated by IntelliJ does not compile in the following method in Kotlin:
class Repository<T : SqlParametrizable<java.lang.String>, PK : Serializable, R : RowMapper<T>, C : BaseCriteria, Q : Query<C>> : JdbcDaoSupport(), IRepository<T, PK, C, Q> {
private fun initializeRepository() {
var genericClass: Class<*> = javaClass
while (Repository<*, *, *, *, *>::class.java.simpleName != genericClass.superclass.simpleName) {
if (genericClass.genericSuperclass is ParameterizedType)
break
genericClass = genericClass.superclass
}
if (genericClass.genericSuperclass is ParameterizedType) {
val parameterizedType = genericClass.genericSuperclass as ParameterizedType
rowMapperClass = parameterizedType.actualTypeArguments[2] as Class<R>
queryClass = parameterizedType.actualTypeArguments[4] as Class<Q>
}
}
I get the following error on the line: while (Repository < *, *, *, *, * > :: class.java.simpleName! = genericClass.superclass.simpleName) { Only classes are allowed on the left hand side of a class literal
Its equivalent in Java that compiles is:
public class Repository<T extends SqlParametrizable<java.lang.String>, PK extends Serializable, R extends RowMapper<T>, C extends BaseCriteria, Q extends Query<C>>
extends JdbcDaoSupport
implements IRepository<T, PK, C, Q> {
private void initializeRepository() {
Class<?> genericClass = getClass();
while (!Repository.class.getSimpleName().equals(genericClass.getSuperclass().getSimpleName())) {
if (genericClass.getGenericSuperclass() instanceof ParameterizedType)
break;
genericClass = genericClass.getSuperclass();
}
if (genericClass.getGenericSuperclass() instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericClass.getGenericSuperclass();
rowMapperClass = (Class<R>) parameterizedType.getActualTypeArguments()[2];
queryClass = (Class<Q>) parameterizedType.getActualTypeArguments()[4];
}
}