diff --git a/pom.xml b/pom.xml
index db0fdf4333..86a83ec605 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
+ * {@link ExpressionMarker} is intended to be used via {@link AotQueryMethodGenerationContext} to maintain usage info, + * making sure the code is only added ({@link #isInUse()}) when {@link #enclosingMethod()} was called for generating + * code. + * + *
+ * ExpressionMarker marker = context.getExpressionMarker(); + * CodeBlock.builder().add("evaluate($L, $S, $L)", marker.enclosingMethod(), queryString, parameters); + *+ * + * @author Christoph Strobl + * @since 4.0 + */ +public class ExpressionMarker { + + private final String typeName; + private boolean inUse = false; + + ExpressionMarker() { + this("ExpressionMarker"); + } + + ExpressionMarker(String typeName) { + this.typeName = typeName; + } + + /** + * @return {@code class ExpressionMarker}. + */ + CodeBlock declaration() { + return CodeBlock.of("class $L{};\n", typeName); + } + + /** + * Calling this method sets the {@link ExpressionMarker} as {@link #isInUse() in-use}. + * + * @return {@code ExpressionMarker.class}. + */ + public CodeBlock marker() { + + if (!inUse) { + inUse = true; + } + return CodeBlock.of("$L.class", typeName); + } + + /** + * Calling this method sets the {@link ExpressionMarker} as {@link #isInUse() in-use}. + * + * @return {@code ExpressionMarker.class.getEnclosingMethod()} + */ + public CodeBlock enclosingMethod() { + return CodeBlock.of("$L.getEnclosingMethod()", marker()); + } + + /** + * @return if the marker is in use. + */ + public boolean isInUse() { + return inUse; + } +} diff --git a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java index a80559ebe8..fd8ad840b7 100644 --- a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java @@ -15,20 +15,24 @@ */ package org.springframework.data.repository.aot.generate; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; import example.UserRepository; import example.UserRepository.User; import java.lang.reflect.Method; import java.util.List; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; - import org.springframework.core.ResolvableType; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.util.TypeInformation; @@ -45,10 +49,12 @@ class AotRepositoryMethodBuilderUnitTests { @BeforeEach void beforeEach() { + repositoryInformation = Mockito.mock(RepositoryInformation.class); methodGenerationContext = Mockito.mock(AotQueryMethodGenerationContext.class); when(methodGenerationContext.getRepositoryInformation()).thenReturn(repositoryInformation); + when(methodGenerationContext.getExpressionMarker()).thenReturn(new ExpressionMarker()); } @Test // GH-3279 @@ -87,4 +93,37 @@ void generatesMethodWithGenerics() throws NoSuchMethodException { .containsPattern("public .*List<.*User> findByFirstnameIn\\(") // .containsPattern(".*List<.*String> firstnames\\)"); } + + @ParameterizedTest // GH-3279 + @MethodSource(value = { "expressionMarkers" }) + void generatesExpressionMarkerIfInUse(ExpressionMarker expressionMarker) throws NoSuchMethodException { + + Method method = UserRepository.class.getMethod("findByFirstname", String.class); + when(methodGenerationContext.getMethod()).thenReturn(method); + when(methodGenerationContext.getReturnType()).thenReturn(ResolvableType.forClass(User.class)); + doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any()); + doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any()); + MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method); + methodMetadata.addParameter(ParameterSpec.builder(String.class, "firstname").build()); + when(methodGenerationContext.getTargetMethodMetadata()).thenReturn(methodMetadata); + when(methodGenerationContext.getExpressionMarker()).thenReturn(expressionMarker); + + AotRepositoryMethodBuilder builder = new AotRepositoryMethodBuilder(methodGenerationContext); + String methodCode = builder.buildMethod().toString(); + if (expressionMarker.isInUse()) { + assertThat(methodCode).contains("class ExpressionMarker{};"); + } else { + assertThat(methodCode).doesNotContain("class ExpressionMarker{};"); + } + } + + static Stream