1
+ /*******************************************************************************
2
+ * Copyright (c) 2017, 2024 Broadcom, Inc.
3
+ * All rights reserved. This program and the accompanying materials
4
+ * are made available under the terms of the Eclipse Public License v1.0
5
+ * which accompanies this distribution, and is available at
6
+ * https://www.eclipse.org/legal/epl-v10.html
7
+ *
8
+ * Contributors:
9
+ * Broadcom, Inc. - initial API and implementation
10
+ *******************************************************************************/
11
+ package org .springframework .ide .vscode .boot .java .handlers ;
12
+
13
+ import java .util .Arrays ;
14
+ import java .util .List ;
15
+ import java .util .Optional ;
16
+ import java .util .concurrent .CompletableFuture ;
17
+
18
+ import org .eclipse .jdt .core .dom .ASTVisitor ;
19
+ import org .eclipse .jdt .core .dom .Annotation ;
20
+ import org .eclipse .jdt .core .dom .CompilationUnit ;
21
+ import org .eclipse .jdt .core .dom .Expression ;
22
+ import org .eclipse .jdt .core .dom .MemberValuePair ;
23
+ import org .eclipse .jdt .core .dom .NormalAnnotation ;
24
+ import org .eclipse .jdt .core .dom .SingleMemberAnnotation ;
25
+ import org .eclipse .lsp4j .CodeLens ;
26
+ import org .eclipse .lsp4j .Command ;
27
+ import org .eclipse .lsp4j .jsonrpc .CancelChecker ;
28
+ import org .springframework .ide .vscode .boot .java .spel .AnnotationParamSpelExtractor ;
29
+ import org .springframework .ide .vscode .boot .java .spel .AnnotationParamSpelExtractor .Snippet ;
30
+ import org .springframework .ide .vscode .commons .java .IJavaProject ;
31
+ import org .springframework .ide .vscode .commons .java .SpringProjectUtil ;
32
+ import org .springframework .ide .vscode .commons .languageserver .java .JavaProjectFinder ;
33
+ import org .springframework .ide .vscode .commons .languageserver .util .SimpleLanguageServer ;
34
+ import org .springframework .ide .vscode .commons .util .BadLocationException ;
35
+ import org .springframework .ide .vscode .commons .util .text .TextDocument ;
36
+
37
+ import com .google .common .collect .ImmutableList ;
38
+ import com .google .gson .JsonPrimitive ;
39
+
40
+ /**
41
+ * @author Udayani V
42
+ */
43
+ public class QueryCodeLensProvider implements CodeLensProvider {
44
+
45
+
46
+ public static final String CMD_ENABLE_COPILOT_FEATURES = "sts/enable/copilot/features" ;
47
+ public static final String EXPLAIN_SPEL_TITLE = "Explain Spel Expression using Copilot" ;
48
+ public static final String EXPLAIN_QUERY_TITLE = "Explain Query using Copilot" ;
49
+
50
+ private static final String QUERY = "Query" ;
51
+ private static final String FQN_QUERY = "org.springframework.data.jpa.repository." + QUERY ;
52
+ private static final String SPEL_EXPRESSION_QUERY_PROMPT = "Explain the following SpEL Expression in detail: \n " ;
53
+ private static final String JPQL_QUERY_PROMPT = "Explain the following JPQL query in detail. If the query contains any SpEL expressions, explain those parts as well: \n " ;
54
+ private static final String HQL_QUERY_PROMPT = "Explain the following HQL query in detail. If the query contains any SpEL expressions, explain those parts as well: \n " ;
55
+ private static final String DEFAULT_QUERY_PROMPT = "Explain the following query in detail: \n " ;
56
+ private static final String CMD = "vscode-spring-boot.query.explain" ;
57
+
58
+ private final AnnotationParamSpelExtractor [] spelExtractors = AnnotationParamSpelExtractor .SPEL_EXTRACTORS ;
59
+
60
+ private final JavaProjectFinder projectFinder ;
61
+
62
+ private static boolean showCodeLenses ;
63
+
64
+ public QueryCodeLensProvider (JavaProjectFinder projectFinder , SimpleLanguageServer server ) {
65
+ this .projectFinder = projectFinder ;
66
+ server .onCommand (CMD_ENABLE_COPILOT_FEATURES , params -> {
67
+ if (params .getArguments ().get (0 ) instanceof JsonPrimitive ) {
68
+ QueryCodeLensProvider .showCodeLenses = ((JsonPrimitive )params .getArguments ().get (0 )).getAsBoolean ();
69
+ }
70
+ return CompletableFuture .completedFuture (showCodeLenses );
71
+ });
72
+ }
73
+
74
+ @ Override
75
+ public void provideCodeLenses (CancelChecker cancelToken , TextDocument document , CompilationUnit cu ,
76
+ List <CodeLens > resultAccumulator ) {
77
+ if (!showCodeLenses ) {
78
+ return ;
79
+ }
80
+ cu .accept (new ASTVisitor () {
81
+
82
+ @ Override
83
+ public boolean visit (SingleMemberAnnotation node ) {
84
+ Arrays .stream (spelExtractors ).map (e -> e .getSpelRegion (node )).filter (o -> o .isPresent ())
85
+ .map (o -> o .get ()).forEach (snippet -> {
86
+ provideCodeLensForSpelExpression (cancelToken , node , document , snippet , resultAccumulator );
87
+ });
88
+
89
+ if (isQueryAnnotation (node )) {
90
+ String queryPrompt = determineQueryPrompt (document );
91
+ provideCodeLensForQuery (cancelToken , node , document , node .getValue (), queryPrompt , resultAccumulator );
92
+ }
93
+
94
+ return super .visit (node );
95
+ }
96
+
97
+ @ Override
98
+ public boolean visit (NormalAnnotation node ) {
99
+ Arrays .stream (spelExtractors ).map (e -> e .getSpelRegion (node )).filter (o -> o .isPresent ())
100
+ .map (o -> o .get ()).forEach (snippet -> {
101
+ provideCodeLensForSpelExpression (cancelToken , node , document , snippet , resultAccumulator );
102
+ });
103
+
104
+ if (isQueryAnnotation (node )) {
105
+ String queryPrompt = determineQueryPrompt (document );
106
+ for (Object value : node .values ()) {
107
+ if (value instanceof MemberValuePair ) {
108
+ MemberValuePair pair = (MemberValuePair ) value ;
109
+ if ("value" .equals (pair .getName ().getIdentifier ())) {
110
+ provideCodeLensForQuery (cancelToken , node , document , pair .getValue (), queryPrompt , resultAccumulator );
111
+ break ;
112
+ }
113
+ }
114
+ }
115
+ }
116
+
117
+ return super .visit (node );
118
+ }
119
+ });
120
+ }
121
+
122
+ protected void provideCodeLensForSpelExpression (CancelChecker cancelToken , Annotation node , TextDocument document , Snippet snippet ,
123
+ List <CodeLens > resultAccumulator ) {
124
+ cancelToken .checkCanceled ();
125
+
126
+ if (snippet != null ) {
127
+ try {
128
+
129
+ CodeLens codeLens = new CodeLens ();
130
+ codeLens .setRange (document .toRange (snippet .offset (), snippet .text ().length ()));
131
+
132
+ Command cmd = new Command ();
133
+ cmd .setTitle (EXPLAIN_SPEL_TITLE );
134
+ cmd .setCommand (CMD );
135
+ cmd .setArguments (ImmutableList .of (SPEL_EXPRESSION_QUERY_PROMPT + snippet .text ()));
136
+ codeLens .setCommand (cmd );
137
+
138
+ resultAccumulator .add (codeLens );
139
+ } catch (BadLocationException e ) {
140
+ e .printStackTrace ();
141
+ }
142
+ }
143
+ }
144
+
145
+ protected void provideCodeLensForQuery (CancelChecker cancelToken , Annotation node , TextDocument document ,
146
+ Expression valueExp , String query , List <CodeLens > resultAccumulator ) {
147
+ cancelToken .checkCanceled ();
148
+
149
+ if (valueExp != null ) {
150
+ try {
151
+
152
+ CodeLens codeLens = new CodeLens ();
153
+ codeLens .setRange (document .toRange (valueExp .getStartPosition (), valueExp .getLength ()));
154
+
155
+ Command cmd = new Command ();
156
+ cmd .setTitle (EXPLAIN_QUERY_TITLE );
157
+ cmd .setCommand (CMD );
158
+ cmd .setArguments (ImmutableList .of (query + valueExp .toString ()));
159
+ codeLens .setCommand (cmd );
160
+
161
+ resultAccumulator .add (codeLens );
162
+ } catch (BadLocationException e ) {
163
+ e .printStackTrace ();
164
+ }
165
+ }
166
+ }
167
+
168
+ private static boolean isQueryAnnotation (Annotation a ) {
169
+ return FQN_QUERY .equals (a .getTypeName ().getFullyQualifiedName ())
170
+ || QUERY .equals (a .getTypeName ().getFullyQualifiedName ());
171
+ }
172
+
173
+ private String determineQueryPrompt (TextDocument document ) {
174
+ Optional <IJavaProject > optProject = projectFinder .find (document .getId ());
175
+ if (optProject .isPresent ()) {
176
+ IJavaProject jp = optProject .get ();
177
+ return SpringProjectUtil .hasDependencyStartingWith (jp , "hibernate-core" , null ) ? HQL_QUERY_PROMPT : JPQL_QUERY_PROMPT ;
178
+ }
179
+ return DEFAULT_QUERY_PROMPT ;
180
+ }
181
+
182
+ }
0 commit comments