Skip to content

Commit a033f29

Browse files
Merge pull request #63 from AxonFramework/43-links-to-message-handlers-that-handle-a-specific-message-type-should-also-work-when-message-is-constructed-using-a-builder-pattern
[#43] Support builder method references, both ways
2 parents 80f9697 + 4508bfd commit a033f29

File tree

6 files changed

+102
-28
lines changed

6 files changed

+102
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
### Added
1414

15+
- [#43] Support builder method references, both ways
1516
- [#38] Aggregate structure hierarchy is now shown in model popup
1617
- [#41] Mark methods annotated with @ResetHandler as used
1718
- [#31] Aggregate structure hierarchy is now shown in model popup

src/main/kotlin/org/axonframework/intellij/ide/plugin/markers/publishers/PublishMethodLineMarkerProvider.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,23 @@ import com.intellij.codeInsight.daemon.LineMarkerProvider
2121
import com.intellij.ide.highlighter.JavaFileType
2222
import com.intellij.openapi.util.NotNullLazyValue
2323
import com.intellij.psi.PsiElement
24+
import com.intellij.psi.PsiIdentifier
25+
import com.intellij.psi.PsiMethod
2426
import org.axonframework.intellij.ide.plugin.AxonIcons
2527
import org.axonframework.intellij.ide.plugin.api.MessageHandlerType
2628
import org.axonframework.intellij.ide.plugin.markers.AxonNavigationGutterIconRenderer
2729
import org.axonframework.intellij.ide.plugin.resolving.MessageHandlerResolver
2830
import org.axonframework.intellij.ide.plugin.resolving.handlers.types.CommandHandlerInterceptor
2931
import org.axonframework.intellij.ide.plugin.resolving.handlers.types.DeadlineHandler
32+
import org.axonframework.intellij.ide.plugin.util.containingClassFqn
3033
import org.axonframework.intellij.ide.plugin.util.handlerResolver
3134
import org.axonframework.intellij.ide.plugin.util.toQualifiedName
3235
import org.jetbrains.kotlin.idea.KotlinFileType
3336
import org.jetbrains.uast.UAnnotation
3437
import org.jetbrains.uast.UCallExpression
3538
import org.jetbrains.uast.UClassLiteralExpression
3639
import org.jetbrains.uast.UIdentifier
40+
import org.jetbrains.uast.UQualifiedReferenceExpression
3741
import org.jetbrains.uast.USimpleNameReferenceExpression
3842
import org.jetbrains.uast.UTypeReferenceExpression
3943
import org.jetbrains.uast.UastCallKind
@@ -81,6 +85,11 @@ class PublishMethodLineMarkerProvider : LineMarkerProvider {
8185

8286
private fun qualifiedNameForKotlin(element: PsiElement): String? {
8387
val uElement = element.toUElementOfType<UIdentifier>() ?: return null
88+
if (element.text.contains("build", ignoreCase = true)) {
89+
// If the method is a builder, show handlers of that class
90+
val referenceExpression = uElement.getParentOfType<UQualifiedReferenceExpression>() ?: return null
91+
return (referenceExpression.resolve() as? PsiMethod?)?.containingClassFqn()
92+
}
8493
val callExpression = uElement.getParentOfType(UCallExpression::class.java, false, USimpleNameReferenceExpression::class.java)
8594
if (callExpression != null && callExpression.kind == UastCallKind.CONSTRUCTOR_CALL) {
8695
return callExpression.classReference.getQualifiedName()
@@ -93,6 +102,11 @@ class PublishMethodLineMarkerProvider : LineMarkerProvider {
93102
}
94103

95104
private fun qualifiedNameForJava(element: PsiElement): String? {
105+
if (element is PsiIdentifier && element.text.contains("build", ignoreCase = true)) {
106+
// If the method is a builder, show handlers of that class
107+
val referenceExpression = element.toUElement()?.getParentOfType<UQualifiedReferenceExpression>() ?: return null
108+
return (referenceExpression.resolve() as? PsiMethod?)?.containingClassFqn()
109+
}
96110
val referenceExpression = getUParentForIdentifier(element) as? USimpleNameReferenceExpression ?: return null
97111
val uElementParent = element.parent.parent.toUElement()
98112
val isConstructor = uElementParent is UCallExpression && uElementParent.kind == UastCallKind.CONSTRUCTOR_CALL

src/main/kotlin/org/axonframework/intellij/ide/plugin/resolving/MessageCreationResolver.kt

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -67,43 +67,19 @@ class MessageCreationResolver(private val project: Project) {
6767
return resolveCreatorsForFqns(classesForQualifiedName)
6868
}
6969

70-
private fun findAll(): List<MessageCreator> {
71-
val handlers = handlerResolver.findAllHandlers()
72-
val payloads = handlers.map { it.payload }.distinct()
73-
return payloads.flatMap { findByPayload(it) }
74-
}
75-
7670
private fun resolveCreatorsForFqns(fqns: List<String>): List<MessageCreator> {
7771
return fqns.flatMap { typeFqn ->
7872
psiFacade.findClasses(typeFqn, project.axonScope()).flatMap { clazz ->
79-
clazz.constructors
73+
// Account for constructors and builder methods (builder(), toBuilder(), etc)
74+
val methods = clazz.constructors + clazz.methods.filter { it.name.contains("build", ignoreCase = true) }
75+
methods
8076
.flatMap { MethodReferencesSearch.search(it, project.axonScope(), true) }
8177
.flatMap { ref -> createCreators(typeFqn, ref.element) }
8278
.distinct()
8379
}
8480
}
8581
}
8682

87-
/**
88-
* Finds the already constructed/found creator in the caches. Useful for quick filtering in line marker popups.
89-
*/
90-
fun findCreatorByElement(element: PsiElement): MessageCreator? {
91-
return constructorsByPayloadCache.values.filter { it.hasUpToDateValue() }
92-
.flatMap { it.value }
93-
.firstOrNull { it.element == element }
94-
}
95-
96-
/**
97-
* This action is VERY expensive. Should only be used if the user does not depend on it or is expected to wait.
98-
* For example, when creating an Event Modeling board based on this info.
99-
*
100-
* @return List of all message creators in an application
101-
*/
102-
fun resolveAllCreators(): List<MessageCreator> {
103-
return findAll()
104-
.flatMap { createCreators(it.payload, it.element) }
105-
}
106-
10783
private fun createCreators(payload: String, element: PsiElement): List<MessageCreator> {
10884
val parentHandlers = element.findParentHandlers()
10985
if (parentHandlers.isEmpty()) {

src/test/kotlin/org/axonframework/intellij/ide/plugin/creators/MessageCreatorResolverTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,41 @@ class MessageCreatorResolverTest : AbstractAxonFixtureTestCase() {
162162
it.renderContainerText() == null
163163
}
164164
}
165+
166+
fun `test resolves builder method as payload creator`() {
167+
addFile("MyBuilderBasedEvent.java", """
168+
public class MyBuilderBasedEvent {
169+
public static class Builder {
170+
public MyBuilderBasedEvent build() {
171+
return new MyBuilderBasedEvent();
172+
}
173+
}
174+
175+
public Builder builder() {
176+
return new Builder();
177+
}
178+
}
179+
""".trimIndent())
180+
181+
addFile(
182+
"MyAggregate.java", """
183+
import test.MyBuilderBasedEvent;
184+
185+
@AggregateRoot
186+
class MyAggregate {
187+
@CommandHandler
188+
public void handle(MyCommand command) {
189+
AggregateLifecycle.apply(MyBuilderBasedEvent.builder().build());
190+
}
191+
}
192+
""".trimIndent(), open = true
193+
)
194+
195+
val creators = project.creatorResolver().getCreatorsForPayload("test.MyBuilderBasedEvent")
196+
Assertions.assertThat(creators).anyMatch {
197+
it.payload == "test.MyBuilderBasedEvent" &&
198+
it.renderText() == "MyAggregate.handle"
199+
it.renderContainerText() == null
200+
}
201+
}
165202
}

src/test/kotlin/org/axonframework/intellij/ide/plugin/markers/ClassLineMarkerProviderTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import org.axonframework.intellij.ide.plugin.AbstractAxonFixtureTestCase
2121
import org.axonframework.intellij.ide.plugin.AxonIcons
2222

2323
class ClassLineMarkerProviderTest : AbstractAxonFixtureTestCase() {
24-
2524
fun `test shows line marker when is used in a command`() {
2625
addFile(
2726
"MyAggregate.kt", """

src/test/kotlin/org/axonframework/intellij/ide/plugin/markers/publishers/PublishMethodLineMarkerProviderTest.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,51 @@ class PublishMethodLineMarkerProviderTest : AbstractAxonFixtureTestCase() {
229229
OptionSummary("MyProcessingGroup", "test", AxonIcons.Handler)
230230
)
231231
}
232+
233+
fun `test shows publish icon on builder method`() {
234+
addFile("MyBuilderBasedEvent.java", """
235+
public class MyBuilderBasedEvent {
236+
public static class Builder {
237+
public MyBuilderBasedEvent build() {
238+
return new MyBuilderBasedEvent();
239+
}
240+
}
241+
242+
public Builder builder() {
243+
return new Builder();
244+
}
245+
}
246+
""".trimIndent())
247+
248+
addFile(
249+
"MyAggregate.java", """
250+
import test.MyBuilderBasedEvent;
251+
252+
@AggregateRoot
253+
class MyAggregate {
254+
@CommandHandler
255+
public void handle(MyCommand command) {
256+
AggregateLifecycle.apply(MyBuilderBasedEvent.builder().build());<caret>
257+
}
258+
}
259+
""".trimIndent(), open = true
260+
)
261+
262+
addFile(
263+
"MyProcessingGroup.java", """
264+
import test.MyBuilderBasedEvent;
265+
266+
class MyProcessingGroup {
267+
@EventHandler
268+
public void handle(MyBuilderBasedEvent event) {
269+
}
270+
}
271+
""".trimIndent()
272+
)
273+
274+
assertThat(hasLineMarker(PublishMethodLineMarkerProvider::class.java)).isTrue
275+
assertThat(getLineMarkerOptions(PublishMethodLineMarkerProvider::class.java)).containsExactly(
276+
OptionSummary("MyProcessingGroup", "test", AxonIcons.Handler)
277+
)
278+
}
232279
}

0 commit comments

Comments
 (0)