Skip to content

Commit

Permalink
Merge pull request #518 from jbl428/feat/jpql-property-description
Browse files Browse the repository at this point in the history
Add support for java getter
  • Loading branch information
shouwn authored Nov 10, 2023
2 parents d428e71 + 9de2e4a commit e15d1c7
Show file tree
Hide file tree
Showing 24 changed files with 297 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package com.linecorp.kotlinjdsl.querymodel.jpql.path.impl
import com.linecorp.kotlinjdsl.Internal
import com.linecorp.kotlinjdsl.querymodel.jpql.entity.Entity
import com.linecorp.kotlinjdsl.querymodel.jpql.path.Path
import kotlin.reflect.KProperty1
import kotlin.reflect.KCallable

@Internal
data class JpqlEntityProperty<T : Any, V> internal constructor(
val entity: Entity<*>,
val property: KProperty1<in T, V>,
val entity: Entity<T>,
val property: KCallable<V>,
) : Path<V & Any>
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package com.linecorp.kotlinjdsl.querymodel.jpql.path.impl

import com.linecorp.kotlinjdsl.Internal
import com.linecorp.kotlinjdsl.querymodel.jpql.path.Path
import kotlin.reflect.KProperty1
import kotlin.reflect.KCallable

@Internal
data class JpqlPathProperty<T : Any, V> internal constructor(
val path: Path<T>,
val property: KProperty1<in T, V>,
val property: KCallable<V>,
) : Path<V & Any>
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlIntrospectorModifier
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlRenderIntrospector
import com.linecorp.kotlinjdsl.render.jpql.introspector.impl.JakartaJpqlIntrospector
import com.linecorp.kotlinjdsl.render.jpql.introspector.impl.JavaxJpqlIntrospector
import com.linecorp.kotlinjdsl.render.jpql.introspector.impl.KotlinStyleJpqlPropertyIntrospector
import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlRenderSerializer
import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer
import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializerModifier
Expand Down Expand Up @@ -246,6 +247,8 @@ private class DefaultModule : JpqlRenderModule {
context.appendIntrospector(JakartaJpqlIntrospector())
}

context.appendIntrospector(KotlinStyleJpqlPropertyIntrospector())

context.addAllSerializer(
JpqlAliasedExpressionSerializer(),
JpqlAndSerializer(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.linecorp.kotlinjdsl.render.jpql.introspector

import com.linecorp.kotlinjdsl.SinceJdsl
import kotlin.reflect.KCallable
import kotlin.reflect.KClass

/**
Expand All @@ -20,4 +21,14 @@ class CombinedJpqlIntrospector(
override fun introspect(type: KClass<*>): JpqlEntityDescription? {
return primary.introspect(type) ?: secondary.introspect(type)
}

/**
* Get the entity information by introspecting KCallable.
*
* If the primary introspector introspects this KCallable, it returns the result of the primary introspector.
* Otherwise, it returns the result of the secondary introspector.
*/
override fun introspect(property: KCallable<*>): JpqlPropertyDescription? {
return primary.introspect(property) ?: secondary.introspect(property)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.linecorp.kotlinjdsl.render.jpql.introspector

import com.linecorp.kotlinjdsl.SinceJdsl
import kotlin.reflect.KCallable

/**
* Abstract class to get the entity information by introspecting KClass.
*/
@SinceJdsl("3.1.0")
abstract class JpqlEntityIntrospector : JpqlIntrospector {
override fun introspect(property: KCallable<*>): JpqlPropertyDescription? = null
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.linecorp.kotlinjdsl.render.jpql.introspector

import com.linecorp.kotlinjdsl.SinceJdsl
import kotlin.reflect.KCallable
import kotlin.reflect.KClass

/**
Expand All @@ -14,4 +15,11 @@ interface JpqlIntrospector {
*/
@SinceJdsl("3.0.0")
fun introspect(type: KClass<*>): JpqlEntityDescription?

/**
* Introspects the KCallable to get the entity information.
* If it cannot introspect this KCallable, it returns null.
*/
@SinceJdsl("3.1.0")
fun introspect(property: KCallable<*>): JpqlPropertyDescription?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.linecorp.kotlinjdsl.render.jpql.introspector

import com.linecorp.kotlinjdsl.SinceJdsl

/**
* Interface to represent the property information.
*/
@SinceJdsl("3.1.0")
interface JpqlPropertyDescription {
@SinceJdsl("3.1.0")
val name: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.linecorp.kotlinjdsl.render.jpql.introspector

import com.linecorp.kotlinjdsl.SinceJdsl
import kotlin.reflect.KClass

/**
* Abstract class to get the entity information by introspecting KCallable.
*/
@SinceJdsl("3.1.0")
abstract class JpqlPropertyIntrospector : JpqlIntrospector {
override fun introspect(type: KClass<*>): JpqlEntityDescription? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.linecorp.kotlinjdsl.SinceJdsl
import com.linecorp.kotlinjdsl.render.AbstractRenderContextElement
import com.linecorp.kotlinjdsl.render.RenderContext
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KCallable
import kotlin.reflect.KClass

/**
Expand All @@ -15,7 +16,8 @@ class JpqlRenderIntrospector(
) : AbstractRenderContextElement(Key) {
companion object Key : RenderContext.Key<JpqlRenderIntrospector>

private val tableLookupCache: MutableMap<KClass<*>, JpqlEntityDescription> = ConcurrentHashMap()
private val entityLookupCache: MutableMap<KClass<*>, JpqlEntityDescription> = ConcurrentHashMap()
private val propertyLookupCache: MutableMap<KCallable<*>, JpqlPropertyDescription> = ConcurrentHashMap()

/**
* Creates a new introspector by combining this introspector and the introspector.
Expand All @@ -38,8 +40,22 @@ class JpqlRenderIntrospector(
return getCachedDescription(clazz)
}

/**
* Introspects the KCallable to get the property information.
*/
@SinceJdsl("3.1.0")
fun introspect(property: KCallable<*>): JpqlPropertyDescription {
return getCachedDescription(property)
}

private fun getCachedDescription(clazz: KClass<*>): JpqlEntityDescription {
return tableLookupCache.computeIfAbsent(clazz) {
return entityLookupCache.computeIfAbsent(clazz) {
getDescription(it)
}
}

private fun getCachedDescription(property: KCallable<*>): JpqlPropertyDescription {
return propertyLookupCache.computeIfAbsent(property) {
getDescription(it)
}
}
Expand All @@ -48,4 +64,9 @@ class JpqlRenderIntrospector(
return introspector.introspect(clazz)
?: throw IllegalStateException("There is no description for ${clazz.java.name}")
}

private fun getDescription(property: KCallable<*>): JpqlPropertyDescription {
return introspector.introspect(property)
?: throw IllegalStateException("There is no description for ${property.name}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package com.linecorp.kotlinjdsl.render.jpql.introspector.impl

import com.linecorp.kotlinjdsl.Internal
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlEntityDescription
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlIntrospector
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlEntityIntrospector
import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotations

/**
* Introspector that introspects KClass using [jakarta.persistence.Entity].
*/
@Internal
class JakartaJpqlIntrospector : JpqlIntrospector {
class JakartaJpqlIntrospector : JpqlEntityIntrospector() {
override fun introspect(type: KClass<*>): JpqlEntityDescription? {
val entity = type.findAnnotations(jakarta.persistence.Entity::class).firstOrNull()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package com.linecorp.kotlinjdsl.render.jpql.introspector.impl

import com.linecorp.kotlinjdsl.Internal
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlEntityDescription
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlIntrospector
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlEntityIntrospector
import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotations

/**
* Introspector that introspects KClass using [javax.persistence.Entity].
*/
@Internal
class JavaxJpqlIntrospector : JpqlIntrospector {
class JavaxJpqlIntrospector : JpqlEntityIntrospector() {
override fun introspect(type: KClass<*>): JpqlEntityDescription? {
val entity = type.findAnnotations(javax.persistence.Entity::class).firstOrNull()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.linecorp.kotlinjdsl.render.jpql.introspector.impl

import com.linecorp.kotlinjdsl.Internal
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlPropertyDescription
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlPropertyIntrospector
import kotlin.reflect.KCallable
import kotlin.reflect.KFunction1
import kotlin.reflect.KProperty1

/**
* Introspector that introspects a property name in KCallable using Kotlin style.
*/
@Internal
class KotlinStyleJpqlPropertyIntrospector : JpqlPropertyIntrospector() {
override fun introspect(property: KCallable<*>): JpqlPropertyDescription? {
return when (property) {
is KProperty1<*, *> -> KotlinStyleProperty(property.name)
is KFunction1<*, *> -> KotlinStyleProperty(resolvePropertyName(property))
else -> null
}
}

private fun resolvePropertyName(getter: KFunction1<*, *>): String =
if (getter.name.startsWith("is")) {
getter.name
} else {
getter.name.removePrefix("get").replaceFirstChar { it.lowercase() }
}
}

private data class KotlinStyleProperty(
override val name: String,
) : JpqlPropertyDescription
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.linecorp.kotlinjdsl.render.jpql.serializer.impl
import com.linecorp.kotlinjdsl.Internal
import com.linecorp.kotlinjdsl.querymodel.jpql.path.impl.JpqlEntityProperty
import com.linecorp.kotlinjdsl.render.RenderContext
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlRenderIntrospector
import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer
import com.linecorp.kotlinjdsl.render.jpql.writer.JpqlWriter
import kotlin.reflect.KClass
Expand All @@ -14,8 +15,11 @@ class JpqlEntityPropertySerializer : JpqlSerializer<JpqlEntityProperty<*, *>> {
}

override fun serialize(part: JpqlEntityProperty<*, *>, writer: JpqlWriter, context: RenderContext) {
val introspector = context.getValue(JpqlRenderIntrospector)
val property = introspector.introspect(part.property)

writer.write(part.entity.alias)
writer.write(".")
writer.write(part.property.name)
writer.write(property.name)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.linecorp.kotlinjdsl.render.jpql.serializer.impl
import com.linecorp.kotlinjdsl.Internal
import com.linecorp.kotlinjdsl.querymodel.jpql.path.impl.JpqlPathProperty
import com.linecorp.kotlinjdsl.render.RenderContext
import com.linecorp.kotlinjdsl.render.jpql.introspector.JpqlRenderIntrospector
import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlRenderSerializer
import com.linecorp.kotlinjdsl.render.jpql.serializer.JpqlSerializer
import com.linecorp.kotlinjdsl.render.jpql.writer.JpqlWriter
Expand All @@ -16,9 +17,11 @@ class JpqlPathPropertySerializer : JpqlSerializer<JpqlPathProperty<*, *>> {

override fun serialize(part: JpqlPathProperty<*, *>, writer: JpqlWriter, context: RenderContext) {
val delegate = context.getValue(JpqlRenderSerializer)
val introspector = context.getValue(JpqlRenderIntrospector)
val property = introspector.introspect(part.property)

delegate.serialize(part.path, writer, context)
writer.write(".")
writer.write(part.property.name)
writer.write(property.name)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import org.assertj.core.api.WithAssertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import kotlin.reflect.KCallable
import kotlin.reflect.KClass

@ExtendWith(MockKExtension::class)
class CombinedJpqlIntrospectorTest : WithAssertions {
Expand All @@ -24,6 +26,10 @@ class CombinedJpqlIntrospectorTest : WithAssertions {
override val name: String = "entityName1"
}

private val propertyDescription1 = object : JpqlPropertyDescription {
override val name: String = "propertyName1"
}

@BeforeEach
fun setUp() {
sut = CombinedJpqlIntrospector(
Expand All @@ -33,9 +39,9 @@ class CombinedJpqlIntrospectorTest : WithAssertions {
}

@Test
fun `introspect() return the description of the primary, when the primary returns non null`() {
fun `introspect(type) return the description of the primary, when the primary returns non null`() {
// given
every { introspector1.introspect(any()) } returns entityDescription1
every { introspector1.introspect(any<KClass<*>>()) } returns entityDescription1

// when
val actual = sut.introspect(Book::class)
Expand All @@ -49,10 +55,10 @@ class CombinedJpqlIntrospectorTest : WithAssertions {
}

@Test
fun `introspect() return the description of the secondary, when the primary returns null`() {
fun `introspect(type) return the description of the secondary, when the primary returns null`() {
// given
every { introspector1.introspect(any()) } returns null
every { introspector2.introspect(any()) } returns entityDescription1
every { introspector1.introspect(any<KClass<*>>()) } returns null
every { introspector2.introspect(any<KClass<*>>()) } returns entityDescription1

// when
val actual = sut.introspect(Book::class)
Expand All @@ -65,4 +71,38 @@ class CombinedJpqlIntrospectorTest : WithAssertions {
introspector2.introspect(Book::class)
}
}

@Test
fun `introspect(property) return the description of the primary, when the primary returns non null`() {
// given
every { introspector1.introspect(any<KCallable<*>>()) } returns propertyDescription1

// when
val actual = sut.introspect(Book::title)

// then
assertThat(actual).isEqualTo(propertyDescription1)

verifySequence {
introspector1.introspect(Book::title)
}
}

@Test
fun `introspect(property) return the description of the secondary, when the primary returns null`() {
// given
every { introspector1.introspect(any<KCallable<*>>()) } returns null
every { introspector2.introspect(any<KCallable<*>>()) } returns propertyDescription1

// when
val actual = sut.introspect(Book::title)

// then
assertThat(actual).isEqualTo(propertyDescription1)

verifySequence {
introspector1.introspect(Book::title)
introspector2.introspect(Book::title)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class JakartaJpqlIntrospectorTest : WithAssertions {
}

@Test
fun introspect() {
fun `introspect() returns name of entity annotation, when entity annotation has name`() {
// given
val type = EntityClass1::class

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class JavaxJpqlIntrospectorTest : WithAssertions {
}

@Test
fun introspect() {
fun `introspect() returns name of entity annotation, when entity annotation has name`() {
// given
val type = EntityClass1::class

Expand Down
Loading

0 comments on commit e15d1c7

Please sign in to comment.