Skip to content

Commit 64fabdf

Browse files
danthe1stBoykoAlex
authored andcommitted
Factor out static methods for Spring Data repository completions
1 parent 75fcb63 commit 64fabdf

11 files changed

+211
-109
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java

Lines changed: 25 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,38 @@
1111
package org.springframework.ide.vscode.boot.java.data;
1212

1313
import java.util.Collection;
14-
import java.util.Optional;
14+
import java.util.List;
1515

1616
import org.eclipse.jdt.core.dom.ASTNode;
1717
import org.eclipse.jdt.core.dom.Annotation;
1818
import org.eclipse.jdt.core.dom.ITypeBinding;
1919
import org.eclipse.jdt.core.dom.TypeDeclaration;
20-
import org.eclipse.lsp4j.CompletionItemKind;
20+
import org.springframework.ide.vscode.boot.java.data.providers.DataRepositoryCompletionProvider;
21+
import org.springframework.ide.vscode.boot.java.data.providers.DataRepositoryQueryStartCompletionProvider;
22+
import org.springframework.ide.vscode.boot.java.data.providers.DataRepositoryStandardCompletionProvider;
23+
import org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive.DataRepositoryPrefixSensitiveCompletionProvider;
2124
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
2225
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
23-
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
2426
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
2527
import org.springframework.ide.vscode.commons.util.BadLocationException;
2628
import org.springframework.ide.vscode.commons.util.text.IDocument;
2729
import org.springframework.ide.vscode.commons.util.text.IRegion;
28-
import org.springframework.util.StringUtils;
2930

3031
/**
3132
* @author Martin Lippert
3233
*/
3334
public class DataRepositoryCompletionProcessor implements CompletionProvider {
3435

36+
private List<DataRepositoryCompletionProvider> completionProviders;
37+
38+
public DataRepositoryCompletionProcessor() {
39+
this.completionProviders = List.of(
40+
new DataRepositoryStandardCompletionProvider(),
41+
new DataRepositoryQueryStartCompletionProvider(),
42+
new DataRepositoryPrefixSensitiveCompletionProvider()
43+
);
44+
}
45+
3546
@Override
3647
public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type,
3748
int offset, IDocument doc, Collection<ICompletionProposal> completions) {
@@ -41,72 +52,20 @@ public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding
4152
public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection<ICompletionProposal> completions) {
4253
TypeDeclaration type = ASTUtils.findDeclaringType(node);
4354
DataRepositoryDefinition repo = getDataRepositoryDefinition(type);
44-
if (repo != null) {
45-
DomainType domainType = repo.getDomainType();
46-
if (domainType != null) {
47-
48-
String prefix = "";
49-
try {
50-
IRegion line = doc.getLineInformationOfOffset(offset);
51-
prefix = doc.get(line.getOffset(), offset - line.getOffset()).trim();
52-
} catch (BadLocationException e) {
53-
// ignore if there is a problem computing the prefix, continue without prefix
54-
}
55-
56-
DomainProperty[] properties = domainType.getProperties();
57-
for (DomainProperty property : properties) {
58-
completions.add(generateCompletionProposal(offset, prefix, repo, property));
59-
}
60-
DataRepositoryPrefixSensitiveCompletionProvider.addPrefixSensitiveProposals(completions, doc, offset, prefix, repo);
55+
if(repo != null && repo.getDomainType() != null){
56+
String prefix = "";
57+
try {
58+
IRegion line = doc.getLineInformationOfOffset(offset);
59+
prefix = doc.get(line.getOffset(), offset - line.getOffset()).trim();
60+
} catch (BadLocationException e) {
61+
// ignore if there is a problem computing the prefix, continue without prefix
62+
}
63+
for(DataRepositoryCompletionProvider provider : completionProviders){
64+
provider.addProposals(completions, doc, offset, prefix, repo);
6165
}
6266
}
6367
}
6468

65-
66-
67-
protected ICompletionProposal generateCompletionProposal(int offset, String prefix, DataRepositoryDefinition repoDef, DomainProperty domainProperty) {
68-
StringBuilder label = new StringBuilder();
69-
label.append("findBy");
70-
label.append(StringUtils.capitalize(domainProperty.getName()));
71-
label.append("(");
72-
label.append(domainProperty.getType().getSimpleName());
73-
label.append(" ");
74-
label.append(StringUtils.uncapitalize(domainProperty.getName()));
75-
label.append(");");
76-
77-
78-
StringBuilder completion = new StringBuilder();
79-
completion.append("List<");
80-
completion.append(repoDef.getDomainType().getSimpleName());
81-
completion.append("> findBy");
82-
completion.append(StringUtils.capitalize(domainProperty.getName()));
83-
completion.append("(");
84-
completion.append(domainProperty.getType().getSimpleName());
85-
completion.append(" ");
86-
completion.append(StringUtils.uncapitalize(domainProperty.getName()));
87-
completion.append(");");
88-
89-
return createProposal(offset, CompletionItemKind.Method, prefix, label.toString(), completion.toString());
90-
}
91-
92-
static ICompletionProposal createProposal(int offset, CompletionItemKind completionItemKind, String prefix, String label, String completion) {
93-
DocumentEdits edits = new DocumentEdits(null, false);
94-
String filter = label;
95-
if (prefix != null && label.startsWith(prefix)) {
96-
edits.replace(offset - prefix.length(), offset, completion);
97-
}
98-
else if (prefix != null && completion.startsWith(prefix)) {
99-
edits.replace(offset - prefix.length(), offset, completion);
100-
filter = completion;
101-
}
102-
else {
103-
edits.insert(offset, completion);
104-
}
105-
106-
DocumentEdits additionalEdits = new DocumentEdits(null, false);
107-
return new FindByCompletionProposal(label, completionItemKind, edits, null, null, Optional.of(additionalEdits), filter);
108-
}
109-
11069
private DataRepositoryDefinition getDataRepositoryDefinition(TypeDeclaration type) {
11170
if (type != null) {
11271
ITypeBinding resolvedType = type.resolveBinding();

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/FindByCompletionProposal.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ public FindByCompletionProposal(String label, CompletionItemKind kind, DocumentE
3939
this.filter = filter;
4040
}
4141

42+
public static ICompletionProposal createProposal(int offset, CompletionItemKind completionItemKind, String prefix, String label, String completion) {
43+
DocumentEdits edits = new DocumentEdits(null, false);
44+
String filter = label;
45+
if (prefix != null && label.startsWith(prefix)) {
46+
edits.replace(offset - prefix.length(), offset, completion);
47+
}
48+
else if (prefix != null && completion.startsWith(prefix)) {
49+
edits.replace(offset - prefix.length(), offset, completion);
50+
filter = completion;
51+
}
52+
else {
53+
edits.insert(offset, completion);
54+
}
55+
56+
DocumentEdits additionalEdits = new DocumentEdits(null, false);
57+
return new FindByCompletionProposal(label, completionItemKind, edits, null, null, Optional.of(additionalEdits), filter);
58+
}
59+
4260
@Override
4361
public String getLabel() {
4462
return label;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Pivotal, 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+
* Pivotal, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data.providers;
12+
13+
import java.util.Collection;
14+
15+
import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
16+
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
17+
import org.springframework.ide.vscode.commons.util.text.IDocument;
18+
19+
/**
20+
* Responsible for creating proposals related to Spring Data repositories.
21+
* @author danthe1st
22+
*/
23+
public interface DataRepositoryCompletionProvider {
24+
25+
void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo);
26+
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Pivotal, 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+
* Pivotal, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data.providers;
12+
13+
import java.util.Collection;
14+
15+
import org.eclipse.lsp4j.CompletionItemKind;
16+
import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
17+
import org.springframework.ide.vscode.boot.java.data.FindByCompletionProposal;
18+
import org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive.DataRepositoryPrefixSensitiveCompletionProvider;
19+
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
20+
import org.springframework.ide.vscode.commons.util.BadLocationException;
21+
import org.springframework.ide.vscode.commons.util.text.IDocument;
22+
23+
/**
24+
* This class creates text roposals for query method subjects, e.g. {@code countBy}.
25+
* @author danthe1st
26+
*/
27+
public class DataRepositoryQueryStartCompletionProvider implements DataRepositoryCompletionProvider{
28+
29+
@Override
30+
public void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo) {
31+
String localPrefix = DataRepositoryPrefixSensitiveCompletionProvider.findLastJavaIdentifierPart(prefix);
32+
for(QueryMethodSubject queryMethodSubject : QueryMethodSubject.QUERY_METHOD_SUBJECTS){
33+
String toInsert = queryMethodSubject.key() + "By";
34+
if(prefix == null || toInsert.startsWith(localPrefix)||isOffsetAfterWhitespace(doc, offset)) {
35+
completions.add(FindByCompletionProposal.createProposal(offset, CompletionItemKind.Text, prefix, toInsert, toInsert));
36+
}
37+
}
38+
}
39+
40+
private boolean isOffsetAfterWhitespace(IDocument doc, int offset) {
41+
try {
42+
return offset > 0 && Character.isWhitespace(doc.getChar(offset-1));
43+
}catch (BadLocationException e) {
44+
return false;
45+
}
46+
}
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Pivotal, 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+
* Pivotal, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.data.providers;
12+
13+
import java.util.Collection;
14+
15+
import org.eclipse.lsp4j.CompletionItemKind;
16+
import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
17+
import org.springframework.ide.vscode.boot.java.data.DomainProperty;
18+
import org.springframework.ide.vscode.boot.java.data.DomainType;
19+
import org.springframework.ide.vscode.boot.java.data.FindByCompletionProposal;
20+
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
21+
import org.springframework.ide.vscode.commons.util.text.IDocument;
22+
import org.springframework.util.StringUtils;
23+
24+
/**
25+
* Provides content assist proposals for querying by a single attribute in Spring Data repositories.
26+
* @author Martin Lippert
27+
*/
28+
public class DataRepositoryStandardCompletionProvider implements DataRepositoryCompletionProvider {
29+
30+
public void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo) {
31+
DomainType domainType = repo.getDomainType();
32+
DomainProperty[] properties = domainType.getProperties();
33+
for (DomainProperty property : properties) {
34+
completions.add(generateCompletionProposal(offset, prefix, repo, property));
35+
}
36+
}
37+
38+
private ICompletionProposal generateCompletionProposal(int offset, String prefix, DataRepositoryDefinition repoDef, DomainProperty domainProperty) {
39+
StringBuilder label = new StringBuilder();
40+
label.append("findBy");
41+
label.append(StringUtils.capitalize(domainProperty.getName()));
42+
label.append("(");
43+
label.append(domainProperty.getType().getSimpleName());
44+
label.append(" ");
45+
label.append(StringUtils.uncapitalize(domainProperty.getName()));
46+
label.append(");");
47+
48+
StringBuilder completion = new StringBuilder();
49+
completion.append("List<");
50+
completion.append(repoDef.getDomainType().getSimpleName());
51+
completion.append("> findBy");
52+
completion.append(StringUtils.capitalize(domainProperty.getName()));
53+
completion.append("(");
54+
completion.append(domainProperty.getType().getSimpleName());
55+
completion.append(" ");
56+
completion.append(StringUtils.uncapitalize(domainProperty.getName()));
57+
completion.append(");");
58+
59+
return FindByCompletionProposal.createProposal(offset, CompletionItemKind.Method, prefix, label.toString(), completion.toString());
60+
}
61+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* Contributors:
99
* Pivotal, Inc. - initial API and implementation
1010
*******************************************************************************/
11-
package org.springframework.ide.vscode.boot.java.data;
11+
package org.springframework.ide.vscode.boot.java.data.providers;
1212

1313
import java.util.List;
1414

@@ -18,10 +18,10 @@
1818
* See https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#appendix.query.method.subject
1919
* @author danthe1st
2020
*/
21-
record QueryMethodSubject(
21+
public record QueryMethodSubject(
2222
String key, String returnType, boolean isTyped) {
2323

24-
static final List<QueryMethodSubject> QUERY_METHOD_SUBJECTS = List.of(
24+
public static final List<QueryMethodSubject> QUERY_METHOD_SUBJECTS = List.of(
2525
QueryMethodSubject.createCollectionSubject("find", "List"),
2626
QueryMethodSubject.createCollectionSubject("read", "List"),
2727
QueryMethodSubject.createCollectionSubject("get", "List"),
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* Contributors:
99
* Pivotal, Inc. - initial API and implementation
1010
*******************************************************************************/
11-
package org.springframework.ide.vscode.boot.java.data;
11+
package org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive;
1212

1313
/**
1414
* Types of predicate keywords Spring JPA repository method names
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
* Contributors:
99
* Pivotal, Inc. - initial API and implementation
1010
*******************************************************************************/
11-
package org.springframework.ide.vscode.boot.java.data;
11+
package org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive;
1212

1313
import java.util.List;
1414
import java.util.Set;
1515

16+
import org.springframework.ide.vscode.boot.java.data.providers.QueryMethodSubject;
17+
1618
/**
17-
* Represents the result of parsing a Spring JPA repository query method
19+
* Represents the result of parsing a Spring Data repository query method
1820
* @author danthe1st
1921
*/
2022
record DataRepositoryMethodNameParseResult(
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* Contributors:
99
* Pivotal, Inc. - initial API and implementation
1010
*******************************************************************************/
11-
package org.springframework.ide.vscode.boot.java.data;
11+
package org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive;
1212

1313
import java.util.ArrayList;
1414
import java.util.EnumSet;
@@ -18,6 +18,10 @@
1818
import java.util.function.Function;
1919
import java.util.stream.Collectors;
2020

21+
import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
22+
import org.springframework.ide.vscode.boot.java.data.DomainProperty;
23+
import org.springframework.ide.vscode.boot.java.data.providers.QueryMethodSubject;
24+
2125
/**
2226
* Class responsible for parsing Spring JPA Repository query methods.
2327
* @author danthe1st

0 commit comments

Comments
 (0)