diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java index ba065ce07..a28d52091 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/Annotations.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java; +import java.util.Map; + /** * Constants containing various fully-qualified annotation names. * @@ -85,6 +87,16 @@ public class Annotations { public static final String SCHEDULED = "org.springframework.scheduling.annotation.Scheduled"; + public static final Map AOP_ANNOTATIONS = Map.of( + "org.aspectj.lang.annotation.Pointcut", "Pointcut", + "org.aspectj.lang.annotation.Before", "Before", + "org.aspectj.lang.annotation.Around", "Around", + "org.aspectj.lang.annotation.After", "After", + "org.aspectj.lang.annotation.AfterReturning", "AfterReturning", + "org.aspectj.lang.annotation.AfterThrowing", "AfterThrowing", + "org.aspectj.lang.annotation.DeclareParents", "DeclareParents" + ); + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index 2f21f605a..cee9d246d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -40,7 +40,7 @@ import org.springframework.ide.vscode.boot.java.handlers.BootJavaWorkspaceSymbol import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider; import org.springframework.ide.vscode.boot.java.handlers.HighlightProvider; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; -import org.springframework.ide.vscode.boot.java.handlers.QueryCodeLensProvider; +import org.springframework.ide.vscode.boot.java.handlers.CopilotCodeLensProvider; import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider; import org.springframework.ide.vscode.boot.java.links.SourceLinks; import org.springframework.ide.vscode.boot.java.livehover.ActiveProfilesProvider; @@ -319,7 +319,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent protected BootJavaCodeLensEngine createCodeLensEngine(SpringSymbolIndex index, JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) { Collection codeLensProvider = new ArrayList<>(); codeLensProvider.add(new WebfluxHandlerCodeLensProvider(index)); - codeLensProvider.add(new QueryCodeLensProvider(projectFinder, server, spelSemanticTokens)); + codeLensProvider.add(new CopilotCodeLensProvider(projectFinder, server, spelSemanticTokens)); return new BootJavaCodeLensEngine(this, codeLensProvider); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/QueryCodeLensProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CopilotCodeLensProvider.java similarity index 61% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/QueryCodeLensProvider.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CopilotCodeLensProvider.java index 43c198d92..d1bc341e6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/QueryCodeLensProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CopilotCodeLensProvider.java @@ -13,7 +13,9 @@ package org.springframework.ide.vscode.boot.java.handlers; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -25,13 +27,17 @@ import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.jsonrpc.CancelChecker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.spel.AnnotationParamSpelExtractor; import org.springframework.ide.vscode.boot.java.spel.AnnotationParamSpelExtractor.Snippet; import org.springframework.ide.vscode.boot.java.spel.SpelSemanticTokens; @@ -49,9 +55,9 @@ import com.google.gson.JsonPrimitive; /** * @author Udayani V */ -public class QueryCodeLensProvider implements CodeLensProvider { +public class CopilotCodeLensProvider implements CodeLensProvider { - protected static Logger logger = LoggerFactory.getLogger(QueryCodeLensProvider.class); + protected static Logger logger = LoggerFactory.getLogger(CopilotCodeLensProvider.class); public static final String CMD_ENABLE_COPILOT_FEATURES = "sts/enable/copilot/features"; @@ -66,13 +72,13 @@ public class QueryCodeLensProvider implements CodeLensProvider { private SpelSemanticTokens spelSemanticTokens; private static boolean showCodeLenses; - - public QueryCodeLensProvider(JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) { + + public CopilotCodeLensProvider(JavaProjectFinder projectFinder, SimpleLanguageServer server, SpelSemanticTokens spelSemanticTokens) { this.projectFinder = projectFinder; this.spelSemanticTokens = spelSemanticTokens; server.onCommand(CMD_ENABLE_COPILOT_FEATURES, params -> { if (params.getArguments().get(0) instanceof JsonPrimitive) { - QueryCodeLensProvider.showCodeLenses = ((JsonPrimitive) params.getArguments().get(0)).getAsBoolean(); + CopilotCodeLensProvider.showCodeLenses = ((JsonPrimitive) params.getArguments().get(0)).getAsBoolean(); } return CompletableFuture.completedFuture(showCodeLenses); }); @@ -84,6 +90,9 @@ public class QueryCodeLensProvider implements CodeLensProvider { if (!showCodeLenses) { return; } + + Map pointcutMap = findPointcuts(cu); + cu.accept(new ASTVisitor() { @Override @@ -91,14 +100,15 @@ public class QueryCodeLensProvider implements CodeLensProvider { Arrays.stream(spelExtractors).map(e -> e.getSpelRegion(node)).filter(o -> o.isPresent()) .map(o -> o.get()).forEach(snippet -> { String additionalContext = parseSpelAndFetchContext(cu, snippet.text()); - provideCodeLensForSpelExpression(cancelToken, node, document, snippet, - additionalContext, resultAccumulator); + provideCodeLensForSpelExpression(cancelToken, node, document, snippet, additionalContext, resultAccumulator); }); if (isQueryAnnotation(node)) { - String queryPrompt = determineQueryPrompt(document); - provideCodeLensForQuery(cancelToken, node, document, node.getValue(), queryPrompt, - resultAccumulator); + QueryType queryType = determineQueryType(document); + provideCodeLensForExpression(cancelToken, node, document, queryType, "", resultAccumulator); + } else if (isAopAnnotation(node)) { + String additionalPointcutContext = extractPointcutReference(node.getValue(), pointcutMap); + provideCodeLensForExpression(cancelToken, node, document, QueryType.AOP, additionalPointcutContext, resultAccumulator); } return super.visit(node); @@ -106,31 +116,40 @@ public class QueryCodeLensProvider implements CodeLensProvider { @Override public boolean visit(NormalAnnotation node) { - Arrays.stream(spelExtractors).map(e -> e.getSpelRegion(node)).filter(o -> o.isPresent()) .map(o -> o.get()).forEach(snippet -> { String additionalContext = parseSpelAndFetchContext(cu, snippet.text()); - provideCodeLensForSpelExpression(cancelToken, node, document, snippet, additionalContext, - resultAccumulator); + provideCodeLensForSpelExpression(cancelToken, node, document, snippet, additionalContext, resultAccumulator); }); if (isQueryAnnotation(node)) { - String queryPrompt = determineQueryPrompt(document); - for (Object value : node.values()) { - if (value instanceof MemberValuePair) { - MemberValuePair pair = (MemberValuePair) value; - if ("value".equals(pair.getName().getIdentifier())) { - provideCodeLensForQuery(cancelToken, node, document, pair.getValue(), queryPrompt, - resultAccumulator); - break; - } - } + QueryType queryType = determineQueryType(document); + provideCodeLensForExpression(cancelToken, node, document, queryType, "", resultAccumulator); + } else if (isAopAnnotation(node)) { + Expression value = getMemberValue(node); + String additionalPointcutContext = null; + if (value != null) { + additionalPointcutContext = extractPointcutReference(value, pointcutMap); } + provideCodeLensForExpression(cancelToken, node, document, QueryType.AOP, additionalPointcutContext, resultAccumulator); } return super.visit(node); } + + private Expression getMemberValue(NormalAnnotation node) { + for (Object value : node.values()) { + if (value instanceof MemberValuePair) { + MemberValuePair pair = (MemberValuePair) value; + if ("pointcut".equals(pair.getName().getIdentifier())) { + return pair.getValue(); + } + } + } + return null; + } + }); } @@ -163,20 +182,26 @@ public class QueryCodeLensProvider implements CodeLensProvider { } } - protected void provideCodeLensForQuery(CancelChecker cancelToken, Annotation node, TextDocument document, - Expression valueExp, String query, List resultAccumulator) { + protected void provideCodeLensForExpression(CancelChecker cancelToken, Annotation node, TextDocument document, + QueryType queryType, String additionalContext, List resultAccumulator) { cancelToken.checkCanceled(); - if (valueExp != null) { + if (node != null) { try { - + + String context = additionalContext != null && !additionalContext.isEmpty() ? String.format( + """ + This is the pointcut definition referenced in the above annotation. \n\n %s \n\nProvide a brief summary of the pointcut's role within the annotation. + Avoid detailed implementation steps and avoid repeating information covered earlier. + """,additionalContext) : ""; + CodeLens codeLens = new CodeLens(); - codeLens.setRange(document.toRange(valueExp.getStartPosition(), valueExp.getLength())); + codeLens.setRange(document.toRange(node.getStartPosition(), node.getLength())); Command cmd = new Command(); - cmd.setTitle(QueryType.DEFAULT.getTitle()); + cmd.setTitle(queryType.getTitle()); cmd.setCommand(CMD); - cmd.setArguments(ImmutableList.of(query + valueExp.toString())); + cmd.setArguments(ImmutableList.of(queryType.getPrompt() + node.toString() + "\n\n" +context)); codeLens.setCommand(cmd); resultAccumulator.add(codeLens); @@ -190,15 +215,15 @@ public class QueryCodeLensProvider implements CodeLensProvider { return FQN_QUERY.equals(a.getTypeName().getFullyQualifiedName()) || QUERY.equals(a.getTypeName().getFullyQualifiedName()); } - - private String determineQueryPrompt(TextDocument document) { + + private QueryType determineQueryType(TextDocument document) { Optional optProject = projectFinder.find(document.getId()); if (optProject.isPresent()) { IJavaProject jp = optProject.get(); - return SpringProjectUtil.hasDependencyStartingWith(jp, "hibernate-core", null) ? QueryType.HQL.getPrompt() - : QueryType.JPQL.getPrompt(); + return SpringProjectUtil.hasDependencyStartingWith(jp, "hibernate-core", null) ? QueryType.HQL + : QueryType.JPQL; } - return QueryType.DEFAULT.getPrompt(); + return QueryType.DEFAULT; } private String parseSpelAndFetchContext(CompilationUnit cu, String spelExpression) { @@ -238,4 +263,49 @@ public class QueryCodeLensProvider implements CodeLensProvider { return methodContext; } + private boolean isAopAnnotation(Annotation a) { + String annotationFQN = a.getTypeName().getFullyQualifiedName(); + return Annotations.AOP_ANNOTATIONS.containsKey(annotationFQN) + || Annotations.AOP_ANNOTATIONS.containsValue(annotationFQN); + } + + private Map findPointcuts(CompilationUnit cu) { + Map pointcutMap = new HashMap<>(); + cu.accept(new ASTVisitor() { + @Override + public boolean visit(MethodDeclaration node) { + for (Object modifierObj : node.modifiers()) { + if (modifierObj instanceof Annotation) { + Annotation annotation = (Annotation) modifierObj; + if ("Pointcut".equals(annotation.getTypeName().getFullyQualifiedName())) { + String methodName = node.getName().getIdentifier(); + pointcutMap.put(methodName, node.toString()); + } + } + } + return super.visit(node); + } + }); + return pointcutMap; + + } + + private String extractPointcutReference(org.eclipse.jdt.core.dom.Expression expression, Map pointcutMap) { + if (expression instanceof MethodInvocation) { + return ((MethodInvocation) expression).getName().getIdentifier(); + } else if (expression instanceof SimpleName) { + return ((SimpleName) expression).getIdentifier(); + } else if (expression instanceof StringLiteral) { + String literalValue = ((StringLiteral) expression).getLiteralValue(); + StringBuilder pointcuts = new StringBuilder(); + for (Map.Entry entry : pointcutMap.entrySet()) { + if (literalValue.contains(entry.getKey())) { + pointcuts.append(entry.getValue()); + } + } + return pointcuts.toString(); + } + return null; + } + } \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/QueryType.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/QueryType.java index feb6f3ffc..31a20bb6c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/QueryType.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/QueryType.java @@ -1,10 +1,11 @@ package org.springframework.ide.vscode.boot.java.handlers; public enum QueryType { - SPEL("Explain SpEL Expression using Copilot", "Explain the following SpEL Expression with a clear summary first, followed by a breakdown of the expression with details: \n\n"), - JPQL("Explain Query using Copilot", "Explain the following JPQL query with a clear summary first, followed by a detailed explanation. If the query contains any SpEL expressions, explain those parts as well: \n\n"), - HQL("Explain Query using Copilot", "Explain the following HQL query with a clear summary first, followed by a detailed explanation. If the query contains any SpEL expressions, explain those parts as well: \n\n"), - DEFAULT("Explain Query using Copilot", "Explain the following query with a clear summary first, followed by a detailed explanation: \n\n"); + SPEL("Explain SpEL Expression with Copilot", "Explain the following SpEL Expression with a clear summary first, followed by a breakdown of the expression with details: \n\n"), + JPQL("Explain Query with Copilot", "Explain the following JPQL query with a clear summary first, followed by a detailed explanation. If the query contains any SpEL expressions, explain those parts as well: \n\n"), + HQL("Explain Query with Copilot", "Explain the following HQL query with a clear summary first, followed by a detailed explanation. If the query contains any SpEL expressions, explain those parts as well: \n\n"), + AOP("Explain AOP annotation with Copilot", "Explain the following AOP annotation with a clear summary first, followed by a detailed contextual explanation of annotation and its purpose: \n\n"), + DEFAULT("Explain Query with Copilot", "Explain the following query with a clear summary first, followed by a detailed explanation: \n\n"); private final String title; private final String prompt; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/handlers/test/QueryCodeLensProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/handlers/test/CopilotCodeLensProviderTest.java similarity index 66% rename from headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/handlers/test/QueryCodeLensProviderTest.java rename to headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/handlers/test/CopilotCodeLensProviderTest.java index ead394037..7a6857e3d 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/handlers/test/QueryCodeLensProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/handlers/test/CopilotCodeLensProviderTest.java @@ -38,7 +38,7 @@ import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; -import org.springframework.ide.vscode.boot.java.handlers.QueryCodeLensProvider; +import org.springframework.ide.vscode.boot.java.handlers.CopilotCodeLensProvider; import org.springframework.ide.vscode.boot.java.handlers.QueryType; import org.springframework.ide.vscode.boot.java.spel.SpelSemanticTokens; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; @@ -59,7 +59,7 @@ import com.google.gson.JsonPrimitive; @ExtendWith(SpringExtension.class) @BootLanguageServerTest @Import(SymbolProviderTestConf.class) -public class QueryCodeLensProviderTest { +public class CopilotCodeLensProviderTest { @Autowired private BootLanguageServerHarness harness; @@ -73,17 +73,17 @@ public class QueryCodeLensProviderTest { private SpelSemanticTokens spelSemanticTokens; private ArgumentCaptor commandHandlerCaptor; - private QueryCodeLensProvider queryCodeLensProvider; + private CopilotCodeLensProvider queryCodeLensProvider; private File directory; @BeforeEach public void setup() throws Exception { harness.intialize(null); - directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spel-query-codelense/").toURI()); + directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spel-query-aop-codelenses/").toURI()); String projectDir = directory.toURI().toString(); server = mock(SimpleLanguageServer.class); commandHandlerCaptor = ArgumentCaptor.forClass(ExecuteCommandHandler.class); - queryCodeLensProvider = new QueryCodeLensProvider(projectFinder, server, spelSemanticTokens); + queryCodeLensProvider = new CopilotCodeLensProvider(projectFinder, server, spelSemanticTokens); // trigger project creation projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); @@ -91,7 +91,7 @@ public class QueryCodeLensProviderTest { CompletableFuture initProject = indexer.waitOperation(); initProject.get(5, TimeUnit.SECONDS); - verify(server).onCommand(eq(QueryCodeLensProvider.CMD_ENABLE_COPILOT_FEATURES), commandHandlerCaptor.capture()); + verify(server).onCommand(eq(CopilotCodeLensProvider.CMD_ENABLE_COPILOT_FEATURES), commandHandlerCaptor.capture()); } @Test @@ -107,9 +107,9 @@ public class QueryCodeLensProviderTest { assertEquals(3, codeLenses.size()); - assertTrue(containsCodeLens(codeLenses.get(0), QueryType.DEFAULT.getTitle(), 9, 8, 9, 108)); - assertTrue(containsCodeLens(codeLenses.get(1), QueryType.DEFAULT.getTitle(), 13, 8, 13, 39)); - assertTrue(containsCodeLens(codeLenses.get(2), QueryType.DEFAULT.getTitle(), 17, 14, 17, 92)); + assertTrue(containsCodeLens(codeLenses.get(0), QueryType.DEFAULT.getTitle(), 9, 1, 9, 109)); + assertTrue(containsCodeLens(codeLenses.get(1), QueryType.DEFAULT.getTitle(), 13, 1, 13, 40)); + assertTrue(containsCodeLens(codeLenses.get(2), QueryType.DEFAULT.getTitle(), 17, 1, 17, 93)); } @Test @@ -194,6 +194,85 @@ public static String concat(String str1,String str2){ assertEquals(expectedPrompt, actualPrompt); } + @Test + public void testShowCodeLensesTrueForAOP() throws Exception { + + setCommandParamsHandler(true); + + String docUri = directory.toPath().resolve("src/main/java/org/test/MyAspect.java").toUri().toString(); + TextDocumentInfo doc = harness.getOrReadFile(new File(new URI(docUri)), LanguageId.JAVA.getId()); + TextDocumentInfo openedDoc = harness.openDocument(doc); + + List codeLenses = harness.getCodeLenses(openedDoc); + + assertEquals(7, codeLenses.size()); + + assertTrue(containsCodeLens(codeLenses.get(0), QueryType.AOP.getTitle(), 9, 1, 9, 53)); + assertTrue(containsCodeLens(codeLenses.get(1), QueryType.AOP.getTitle(), 14, 1, 14, 24)); + assertTrue(containsCodeLens(codeLenses.get(2), QueryType.AOP.getTitle(), 19, 1, 19, 51)); + assertTrue(containsCodeLens(codeLenses.get(3), QueryType.AOP.getTitle(), 27, 1, 27, 50)); + assertTrue(containsCodeLens(codeLenses.get(4), QueryType.AOP.getTitle(), 32, 1, 32, 92)); + assertTrue(containsCodeLens(codeLenses.get(5), QueryType.AOP.getTitle(), 37, 1, 37, 86)); + assertTrue(containsCodeLens(codeLenses.get(6), QueryType.AOP.getTitle(), 42, 1, 42, 65)); + } + + @Test + public void testShowCodeLensesTrueForAopPointcutExamples() throws Exception { + + setCommandParamsHandler(true); + + String docUri = directory.toPath().resolve("src/main/java/org/test/PointcutExamples.java").toUri().toString(); + TextDocumentInfo doc = harness.getOrReadFile(new File(new URI(docUri)), LanguageId.JAVA.getId()); + TextDocumentInfo openedDoc = harness.openDocument(doc); + + String expectedPrompt = """ +Explain the following AOP annotation with a clear summary first, followed by a detailed contextual explanation of annotation and its purpose: \n +@Pointcut("cflow(execution(* com.example..*.*(..)))") + + """; + + String expectedPromptWithContext = """ +Explain the following AOP annotation with a clear summary first, followed by a detailed contextual explanation of annotation and its purpose: \n +@AfterReturning(pointcut="targetService()",returning="result") + + This is the pointcut definition referenced in the above annotation. \n + @Pointcut("target(com.example.service.MyService)") public void targetService(){ +} + \n +Provide a brief summary of the pointcut's role within the annotation. + Avoid detailed implementation steps and avoid repeating information covered earlier. + """; + String expectedPromptWithMultiPointcutRef = """ +Explain the following AOP annotation with a clear summary first, followed by a detailed contextual explanation of annotation and its purpose: \n +@Pointcut("serviceLayer() || repositoryLayer()") + + This is the pointcut definition referenced in the above annotation. \n + @Pointcut("within(com.example.repository..*)") public void repositoryLayer(){ +} +@Pointcut("execution(* com.example.service.*.*(..))") public void serviceLayer(){ +} + \n +Provide a brief summary of the pointcut's role within the annotation. + Avoid detailed implementation steps and avoid repeating information covered earlier. + """; + + List codeLenses = harness.getCodeLenses(openedDoc); + + assertEquals(8, codeLenses.size()); + + assertTrue(containsCodeLens(codeLenses.get(0), QueryType.AOP.getTitle(), 4, 1, 4, 54)); + assertTrue(containsCodeLens(codeLenses.get(3), QueryType.AOP.getTitle(), 15, 1, 15, 64)); + + String actualPrompt = codeLenses.get(0).getCommand().getArguments().get(0).toString(); + String actualPromptWithContext = codeLenses.get(3).getCommand().getArguments().get(0).toString(); + String actualPromptWithMultiPointcutRef = codeLenses.get(7).getCommand().getArguments().get(0).toString(); + + assertEquals(expectedPrompt, actualPrompt); + assertEquals(expectedPromptWithContext, actualPromptWithContext); + assertEquals(expectedPromptWithMultiPointcutRef, actualPromptWithMultiPointcutRef); + + } + @Test public void testShowCodeLensesFalseForQuery() throws Exception { @@ -222,6 +301,20 @@ public static String concat(String str1,String str2){ assertEquals(0, codeLenses.size()); } + @Test + public void testShowCodeLensesFalseForAOP() throws Exception { + + setCommandParamsHandler(false); + + String docUri = directory.toPath().resolve("src/main/java/org/test/MyAspect.java").toUri().toString(); + TextDocumentInfo doc = harness.getOrReadFile(new File(new URI(docUri)), LanguageId.JAVA.getId()); + TextDocumentInfo openedDoc = harness.openDocument(doc); + + List codeLenses = harness.getCodeLenses(openedDoc); + + assertEquals(0, codeLenses.size()); + } + private void setCommandParamsHandler(boolean value) throws InterruptedException, ExecutionException { ExecuteCommandHandler handler = commandHandlerCaptor.getValue(); ExecuteCommandParams params = new ExecuteCommandParams(); diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/mvnw b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/mvnw similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/mvnw rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/mvnw diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/mvnw.cmd b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/mvnw.cmd similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/mvnw.cmd rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/mvnw.cmd diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/pom.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/pom.xml similarity index 92% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/pom.xml rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/pom.xml index ae105dfcb..b03c68a51 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/pom.xml +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/pom.xml @@ -35,6 +35,11 @@ org.springframework.boot spring-boot-starter-data-jpa + + + org.springframework + spring-context + diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/src/main/java/org/test/Application.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/Application.java similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/src/main/java/org/test/Application.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/Application.java diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/MyAspect.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/MyAspect.java new file mode 100644 index 000000000..ab773cf50 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/MyAspect.java @@ -0,0 +1,56 @@ +package org.test; + +import org.aspectj.lang.annotation.*; +import org.springframework.stereotype.Component; + +@Component +@Aspect +public class MyAspect { + + @Pointcut("execution(* com.example.aopdemo..*(..))") + public void myPointcut() { + // Pointcut definition + } + + @Before("myPointcut()") + public void beforeAdvice() { + System.out.println("Before advice executed"); + } + + @Around("execution(* com.example.aopdemo..*(..))") + public Object aroundAdvice(org.aspectj.lang.ProceedingJoinPoint joinPoint) throws Throwable { + System.out.println("Around advice: before method execution"); + Object result = joinPoint.proceed(); + System.out.println("Around advice: after method execution"); + return result; + } + + @After("execution(* com.example.aopdemo..*(..))") + public void afterAdvice() { + System.out.println("After advice executed"); + } + + @AfterReturning(pointcut = "execution(* com.example.aopdemo..*(..))", returning = "result") + public void afterReturningAdvice(Object result) { + System.out.println("After returning advice executed, returned: " + result); + } + + @AfterThrowing(pointcut = "execution(* com.example.aopdemo..*(..))", throwing = "ex") + public void afterThrowingAdvice(Exception ex) { + System.out.println("After throwing advice executed, exception: " + ex.getMessage()); + } + + @DeclareParents(value = "org.test..*", defaultImpl = Test.class) + public static MyInterface mixin; +} + +interface MyInterface { + void someMethod(); +} + +class Test implements MyInterface { + @Override + public void someMethod() { + System.out.println("Default implementation"); + } +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/src/main/java/org/test/Owner.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/Owner.java similarity index 89% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/src/main/java/org/test/Owner.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/Owner.java index 2c9f0e4b4..092a13453 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/src/main/java/org/test/Owner.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/Owner.java @@ -123,16 +123,4 @@ public class Owner extends Person { return null; } - @Override - public String toString() { - return new ToStringCreator(this).append("id", this.getId()) - .append("new", this.isNew()) - .append("lastName", this.getLastName()) - .append("firstName", this.getFirstName()) - .append("address", this.address) - .append("city", this.city) - .append("telephone", this.telephone) - .toString(); - } - } diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/src/main/java/org/test/OwnerRepository.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/OwnerRepository.java similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/src/main/java/org/test/OwnerRepository.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/OwnerRepository.java diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/Pet.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/Pet.java new file mode 100644 index 000000000..c826c1d40 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/Pet.java @@ -0,0 +1,62 @@ +package org.test; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.samples.petclinic.model.NamedEntity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderBy; +import jakarta.persistence.Table; + +@Entity +@Table(name = "pets") +public class Pet extends NamedEntity { + + @Column(name = "birth_date") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate birthDate; + + @ManyToOne + @JoinColumn(name = "type_id") + private PetType type; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "pet_id") + @OrderBy("visit_date ASC") + private Set visits = new LinkedHashSet<>(); + + public void setBirthDate(LocalDate birthDate) { + this.birthDate = birthDate; + } + + public LocalDate getBirthDate() { + return this.birthDate; + } + + public PetType getType() { + return this.type; + } + + public void setType(PetType type) { + this.type = type; + } + + public Collection getVisits() { + return this.visits; + } + + public void addVisit(Visit visit) { + getVisits().add(visit); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/PointcutExamples.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/PointcutExamples.java new file mode 100644 index 000000000..95b19be9d --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/PointcutExamples.java @@ -0,0 +1,38 @@ +package org.test; + +public class PointcutExamples { + + @Pointcut("cflow(execution(* com.example..*.*(..)))") + public void myPointcutFlow() {} + + @Pointcut("target(com.example.service.MyService)") + public void targetService() {} + + @Before("myPointcutFlow()") + public void beforeAdviceFlow() { + System.out.println("Before advice triggered by control flow."); + } + + @AfterReturning(pointcut="targetService()", returning="result") + public void afterReturningAdvice(Object result) { + System.out.println("After returning advice: " + result); + } + + @Around("targetService()") + public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { + System.out.println("Around advice: before method execution"); + Object result = joinPoint.proceed(); + System.out.println("Around advice: after method execution"); + return result; + } + + @Pointcut("execution(* com.example.service.*.*(..))") + public void serviceLayer() {} + + @Pointcut("within(com.example.repository..*)") + public void repositoryLayer() {} + + @Pointcut("serviceLayer() || repositoryLayer()") + public void applicationLayer() {} + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/src/main/java/org/test/SpelController.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/SpelController.java similarity index 100% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-codelense/src/main/java/org/test/SpelController.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spel-query-aop-codelenses/src/main/java/org/test/SpelController.java