Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
property completion in JPA repository methods
  • Loading branch information
danthe1st committed Feb 21, 2023
commit f025bf6f89766a9f32c3f3f03e9ae224b13cb061
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ record DataRepositoryMethodNameParseResult(
*/
boolean performFullCompletion,
/**
* the last entered word, which completion options should be shown for
* the last entered word, which completion options should be used for completing the expression.
*
* e.g. {@code First} in {@code findByFirst} which could be completed to {@code findByFirstName}
*/
String lastWord,
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,11 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.data;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
Expand All @@ -43,43 +39,69 @@ static void addPrefixSensitiveProposals(Collection<ICompletionProposal> completi
}
DataRepositoryMethodNameParseResult parseResult = new JPARepositoryMethodParser(localPrefix, repoDef).parseLocalPrefixForCompletion();
if(parseResult != null && parseResult.performFullCompletion()){
String methodName=localPrefix;
DocumentEdits edits = new DocumentEdits(null, false);
String signature = buildSignature(methodName, repoDef, parseResult);
StringBuilder newText = new StringBuilder();
newText.append(parseResult.subjectType().returnType());
if (parseResult.subjectType().isTyped()) {
newText.append("<");
newText.append(repoDef.getDomainType().getSimpleName());
newText.append(">");
Map<String, DomainProperty> propertiesByName = getPropertiesByName(repoDef.getDomainType().getProperties());
addMethodCompletionProposal(completions, offset, repoDef, localPrefix, parseResult, propertiesByName);

if (parseResult.lastWord() == null || !propertiesByName.containsKey(parseResult.lastWord())) {
addPropertyProposals(completions, offset, repoDef, parseResult);
}
newText.append(" ");
newText.append(signature);
newText.append(";");
edits.replace(offset - localPrefix.length(), offset, newText.toString());
DocumentEdits additionalEdits = new DocumentEdits(null, false);
ICompletionProposal proposal = new FindByCompletionProposal(methodName, CompletionItemKind.Method, edits, null, null, Optional.of(additionalEdits), signature);
completions.add(proposal);
}
}

private static String buildSignature(String methodName, DataRepositoryDefinition repoDef, DataRepositoryMethodNameParseResult parseResult) {
private static void addPropertyProposals(Collection<ICompletionProposal> completions, int offset, DataRepositoryDefinition repoDef, DataRepositoryMethodNameParseResult parseResult) {
for(DomainProperty property : repoDef.getDomainType().getProperties()){
String lastWord = parseResult.lastWord();
if (lastWord == null) {
lastWord = "";
}
if (property.getName().startsWith(lastWord)) {
DocumentEdits edits = new DocumentEdits(null, false);
edits.replace(offset - lastWord.length(), offset, property.getName());
DocumentEdits additionalEdits = new DocumentEdits(null, false);
ICompletionProposal proposal = new FindByCompletionProposal(property.getName(), CompletionItemKind.Text, edits, "property " + property.getName(), null, Optional.of(additionalEdits), lastWord);
completions.add(proposal);
}
}
}

private static void addMethodCompletionProposal(Collection<ICompletionProposal> completions, int offset, DataRepositoryDefinition repoDef, String localPrefix, DataRepositoryMethodNameParseResult parseResult, Map<String, DomainProperty> propertiesByName) {
String methodName = localPrefix;
DocumentEdits edits = new DocumentEdits(null, false);
String signature = buildSignature(methodName, propertiesByName, parseResult);
StringBuilder newText = new StringBuilder();
newText.append(parseResult.subjectType().returnType());
if (parseResult.subjectType().isTyped()) {
newText.append("<");
newText.append(repoDef.getDomainType().getSimpleName());
newText.append(">");
}
newText.append(" ");
newText.append(signature);
newText.append(";");
edits.replace(offset - localPrefix.length(), offset, newText.toString());
DocumentEdits additionalEdits = new DocumentEdits(null, false);
ICompletionProposal proposal = new FindByCompletionProposal(methodName, CompletionItemKind.Method, edits, null, null, Optional.of(additionalEdits), signature);
completions.add(proposal);
}

private static Map<String, DomainProperty> getPropertiesByName(DomainProperty[] properties) {
Map<String, DomainProperty> propertiesByName = new HashMap<>();
for(DomainProperty prop : properties){
propertiesByName.put(prop.getName(), prop);
}
return propertiesByName;
}

private static String buildSignature(String methodName, Map<String, DomainProperty> properties, DataRepositoryMethodNameParseResult parseResult) {
StringBuilder signatureBuilder = new StringBuilder();
signatureBuilder.append(methodName);
signatureBuilder.append("(");
List<String> parameters = parseResult.parameters();
for(int i = 0; i < parameters.size(); i++){
String param = parameters.get(i);
DomainProperty[] properties = repoDef.getDomainType().getProperties();
boolean found = false;
for(int j = 0; j < properties.length; j++){
DomainProperty prop = properties[j];
if(prop.getName().equalsIgnoreCase(param)) {
signatureBuilder.append(prop.getType().getSimpleName());
found = true;
}
}
if (!found) {
if (properties.containsKey(param)) {
signatureBuilder.append(properties.get(param).getType().getSimpleName());
} else {
signatureBuilder.append("Object");
}
signatureBuilder.append(" ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ public JPARepositoryMethodParser(String localPrefix, DataRepositoryDefinition re
propertiesGroupedByFirstWord = groupPropertiesByFirstWord(repoDef);
}

private Map<String, List<DomainProperty>> groupPropertiesByFirstWord(DataRepositoryDefinition repoDef) {
Map<String, List<DomainProperty>> grouped = new HashMap<>();
for(DomainProperty property : repoDef.getDomainType().getProperties()){
String firstWord = findFirstWord(property.getName());
grouped.putIfAbsent(firstWord, new ArrayList<>());
grouped.get(firstWord).add(property);
}
return grouped;
}

DataRepositoryMethodNameParseResult parseLocalPrefixForCompletion() {
int subjectPredicateSplitIndex = prefix.indexOf("By");
if (subjectPredicateSplitIndex == -1) {
Expand Down Expand Up @@ -177,16 +187,6 @@ private <T> T findByLargestFirstWord(Map<String, List<T>> toSearch, Function<T,
return ret;
}

private Map<String, List<DomainProperty>> groupPropertiesByFirstWord(DataRepositoryDefinition repoDef) {
Map<String, List<DomainProperty>> propertiesGroupedByFirstWord = new HashMap<>();
for(DomainProperty property : repoDef.getDomainType().getProperties()){
String firstWord = findFirstWord(property.getName());
propertiesGroupedByFirstWord.putIfAbsent(firstWord, new ArrayList<>());
propertiesGroupedByFirstWord.get(firstWord).add(property);
}
return propertiesGroupedByFirstWord;
}

private static String findFirstWord(String expression) {
int firstWordEnd;
for (firstWordEnd = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ void testKeywordInPredicate() throws Exception {
checkCompletions("findByThisCustomerIsSpecial", "List<Customer> findByThisCustomerIsSpecial(boolean thisCustomerIsSpecial);");
}

@Test
void testPropertyProposals() throws Exception {
checkCompletions("findByFirst", "findByFirstName");
checkCompletions("findByFirstNameAndL", "findByFirstNameAndLastName");
checkCompletions("findBy",
"findByFirstName",
"findByLastName");
}

private void checkCompletions(String alredyPresent, String... expectedCompletions) throws Exception {
prepareCase("{\n}", "{\n\t" + alredyPresent + "<*>");
assertContainsAnnotationCompletions(Arrays.stream(expectedCompletions).map(expected -> expected + "<*>").toArray(String[]::new));
Expand Down