1
+ /*******************************************************************************
2
+ * Copyright (c) 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 .conditionalonresource ;
12
+
13
+ import static org .springframework .ide .vscode .commons .util .StringUtil .camelCaseToHyphens ;
14
+
15
+ import java .nio .file .Paths ;
16
+ import java .util .*;
17
+ import java .util .stream .Collectors ;
18
+
19
+ import org .eclipse .jdt .core .dom .ASTNode ;
20
+ import org .eclipse .jdt .core .dom .Annotation ;
21
+ import org .eclipse .jdt .core .dom .ITypeBinding ;
22
+ import org .eclipse .jdt .core .dom .MemberValuePair ;
23
+ import org .eclipse .jdt .core .dom .QualifiedName ;
24
+ import org .eclipse .jdt .core .dom .SimpleName ;
25
+ import org .eclipse .jdt .core .dom .StringLiteral ;
26
+ import org .eclipse .lsp4j .TextDocumentIdentifier ;
27
+ import org .openrewrite .yaml .internal .grammar .JsonPathParser ;
28
+ import org .slf4j .Logger ;
29
+ import org .slf4j .LoggerFactory ;
30
+ import org .springframework .ide .vscode .boot .java .annotations .AnnotationAttributeCompletionProposal ;
31
+ import org .springframework .ide .vscode .boot .java .handlers .CompletionProvider ;
32
+ import org .springframework .ide .vscode .boot .metadata .ProjectBasedPropertyIndexProvider ;
33
+ import org .springframework .ide .vscode .boot .metadata .PropertyInfo ;
34
+ import org .springframework .ide .vscode .boot .metadata .SpringPropertyIndexProvider ;
35
+ import org .springframework .ide .vscode .commons .java .IClasspathUtil ;
36
+ import org .springframework .ide .vscode .commons .java .IJavaProject ;
37
+ import org .springframework .ide .vscode .commons .languageserver .completion .DocumentEdits ;
38
+ import org .springframework .ide .vscode .commons .languageserver .completion .ICompletionProposal ;
39
+ import org .springframework .ide .vscode .commons .languageserver .java .JavaProjectFinder ;
40
+ import org .springframework .ide .vscode .commons .util .BadLocationException ;
41
+ import org .springframework .ide .vscode .commons .util .FuzzyMap ;
42
+ import org .springframework .ide .vscode .commons .util .FuzzyMap .Match ;
43
+ import org .springframework .ide .vscode .commons .util .text .IDocument ;
44
+ import org .springframework .ide .vscode .commons .util .text .TextDocument ;
45
+
46
+ /**
47
+ * @author Karthik Sankaranarayanan
48
+ */
49
+ public class ConditionalOnResourceProcessor implements CompletionProvider {
50
+
51
+ private static final Logger log = LoggerFactory .getLogger (ConditionalOnResourceProcessor .class );
52
+
53
+ private final JavaProjectFinder projectFinder ;
54
+
55
+ public ConditionalOnResourceProcessor (JavaProjectFinder projectFinder ) {
56
+ this .projectFinder = projectFinder ;
57
+ }
58
+
59
+ @ Override
60
+ public void provideCompletions (ASTNode node , Annotation annotation , ITypeBinding type ,
61
+ int offset , TextDocument doc , Collection <ICompletionProposal > completions ) {
62
+
63
+ try {
64
+ Optional <IJavaProject > optionalProject = this .projectFinder .find (doc .getId ());
65
+ if (optionalProject .isEmpty ()) {
66
+ return ;
67
+ }
68
+
69
+ IJavaProject project = optionalProject .get ();
70
+
71
+ // case: @ConditionalOnResource(resources=<*>)
72
+ if (node instanceof SimpleName && node .getParent () instanceof MemberValuePair
73
+ && ("resources" .equals (((MemberValuePair )node .getParent ()).getName ().toString ()))) {
74
+ computeProposalsForSimpleName (project , node , completions , offset , doc );
75
+ }
76
+ // case: @ConditionalOnResource(resources=<*>)
77
+ else if (node instanceof SimpleName && node .getParent () instanceof QualifiedName && node .getParent ().getParent () instanceof MemberValuePair
78
+ && ("resources" .equals (((MemberValuePair )node .getParent ().getParent ()).getName ().toString ()))) {
79
+ computeProposalsForSimpleName (project , node .getParent (), completions , offset , doc );
80
+ }
81
+ // case:@ConditionalOnResource(resources="prefix<*>")
82
+ else if (node instanceof StringLiteral && node .getParent () instanceof MemberValuePair
83
+ && ("resources" .equals (((MemberValuePair )node .getParent ()).getName ().toString ()))) {
84
+ if (node .toString ().startsWith ("\" " ) && node .toString ().endsWith ("\" " )) {
85
+ computeProposalsForStringLiteral (project , (StringLiteral ) node , completions , offset , doc );
86
+ }
87
+ }
88
+ }
89
+ catch (Exception e ) {
90
+ log .error ("problem while looking for ConditionalOnResource annotation proposals" , e );
91
+ }
92
+ }
93
+
94
+ private void addClasspathResourceProposals (IJavaProject project , TextDocument doc , int startOffset , int endOffset , String prefix , boolean includeQuotes , Collection <ICompletionProposal > completions ) {
95
+ String [] resources = findResources (project , prefix );
96
+
97
+ double score = resources .length + 1000 ;
98
+ for (String resource : resources ) {
99
+
100
+ DocumentEdits edits = new DocumentEdits (doc , false );
101
+
102
+ if (includeQuotes ) {
103
+ edits .replace (startOffset , endOffset , "\" classpath:" + resource + "\" " );
104
+ }
105
+ else {
106
+ edits .replace (startOffset , endOffset , "classpath:" + resource );
107
+ }
108
+
109
+ String label = "classpath:" + resource ;
110
+
111
+ ICompletionProposal proposal = new AnnotationAttributeCompletionProposal (edits , label , label , null , score --);
112
+ completions .add (proposal );
113
+ }
114
+
115
+ }
116
+
117
+ private void computeProposalsForSimpleName (IJavaProject project , ASTNode node , Collection <ICompletionProposal > completions , int offset , TextDocument doc ) {
118
+ int startOffset = node .getStartPosition ();
119
+ int endOffset = node .getStartPosition () + node .getLength ();
120
+
121
+ String unfilteredPrefix = node .toString ().substring (0 , offset - node .getStartPosition ());
122
+ addClasspathResourceProposals (project , doc , startOffset , endOffset , unfilteredPrefix , true , completions );
123
+ }
124
+
125
+ private void computeProposalsForStringLiteral (IJavaProject project , StringLiteral node , Collection <ICompletionProposal > completions , int offset , TextDocument doc ) throws BadLocationException {
126
+ String prefix = identifyPropertyPrefix (doc .get (node .getStartPosition () + 1 , offset - (node .getStartPosition () + 1 )), offset - (node .getStartPosition () + 1 ));
127
+
128
+ int startOffset = offset - prefix .length ();
129
+ int endOffset = offset ;
130
+
131
+ String unfilteredPrefix = node .getLiteralValue ().substring (0 , offset - (node .getStartPosition () + 1 ));
132
+ addClasspathResourceProposals (project , doc , startOffset , endOffset , unfilteredPrefix , false , completions );
133
+ }
134
+
135
+ public String identifyPropertyPrefix (String nodeContent , int offset ) {
136
+ String result = nodeContent .substring (0 , offset );
137
+
138
+ int i = offset - 1 ;
139
+ while (i >= 0 ) {
140
+ char c = nodeContent .charAt (i );
141
+ if (c == '}' || c == '{' || c == '$' || c == '#' ) {
142
+ result = result .substring (i + 1 , offset );
143
+ break ;
144
+ }
145
+ i --;
146
+ }
147
+
148
+ return result ;
149
+ }
150
+
151
+ private String [] findResources (IJavaProject project , String prefix ) {
152
+ String [] resources = IClasspathUtil .getClasspathResources (project .getClasspath ()).stream ()
153
+ .distinct ()
154
+ .sorted (new Comparator <String >() {
155
+ @ Override
156
+ public int compare (String o1 , String o2 ) {
157
+ return Paths .get (o1 ).compareTo (Paths .get (o2 ));
158
+ }
159
+ })
160
+ .map (r -> r .replaceAll ("\\ \\ " , "/" ))
161
+ .filter (r -> ("classpath:" + r ).contains (prefix ))
162
+ .toArray (String []::new );
163
+
164
+ return resources ;
165
+ }
166
+
167
+ }
0 commit comments