Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Factor out static methods for Spring Data repository completions #983

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,38 @@
package org.springframework.ide.vscode.boot.java.data;

import java.util.Collection;
import java.util.Optional;
import java.util.List;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.boot.java.data.providers.DataRepositoryCompletionProvider;
import org.springframework.ide.vscode.boot.java.data.providers.DataRepositoryQueryStartCompletionProvider;
import org.springframework.ide.vscode.boot.java.data.providers.DataRepositoryStandardCompletionProvider;
import org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive.DataRepositoryPrefixSensitiveCompletionProvider;
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.IRegion;
import org.springframework.util.StringUtils;

/**
* @author Martin Lippert
*/
public class DataRepositoryCompletionProcessor implements CompletionProvider {

private List<DataRepositoryCompletionProvider> completionProviders;

public DataRepositoryCompletionProcessor() {
this.completionProviders = List.of(
new DataRepositoryStandardCompletionProvider(),
new DataRepositoryQueryStartCompletionProvider(),
new DataRepositoryPrefixSensitiveCompletionProvider()
);
}

@Override
public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type,
int offset, IDocument doc, Collection<ICompletionProposal> completions) {
Expand All @@ -41,72 +52,20 @@ public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding
public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection<ICompletionProposal> completions) {
TypeDeclaration type = ASTUtils.findDeclaringType(node);
DataRepositoryDefinition repo = getDataRepositoryDefinition(type);
if (repo != null) {
DomainType domainType = repo.getDomainType();
if (domainType != null) {

String prefix = "";
try {
IRegion line = doc.getLineInformationOfOffset(offset);
prefix = doc.get(line.getOffset(), offset - line.getOffset()).trim();
} catch (BadLocationException e) {
// ignore if there is a problem computing the prefix, continue without prefix
}

DomainProperty[] properties = domainType.getProperties();
for (DomainProperty property : properties) {
completions.add(generateCompletionProposal(offset, prefix, repo, property));
}
DataRepositoryPrefixSensitiveCompletionProvider.addPrefixSensitiveProposals(completions, doc, offset, prefix, repo);
if(repo != null && repo.getDomainType() != null){
String prefix = "";
try {
IRegion line = doc.getLineInformationOfOffset(offset);
prefix = doc.get(line.getOffset(), offset - line.getOffset()).trim();
} catch (BadLocationException e) {
// ignore if there is a problem computing the prefix, continue without prefix
}
for(DataRepositoryCompletionProvider provider : completionProviders){
provider.addProposals(completions, doc, offset, prefix, repo);
}
}
}



protected ICompletionProposal generateCompletionProposal(int offset, String prefix, DataRepositoryDefinition repoDef, DomainProperty domainProperty) {
StringBuilder label = new StringBuilder();
label.append("findBy");
label.append(StringUtils.capitalize(domainProperty.getName()));
label.append("(");
label.append(domainProperty.getType().getSimpleName());
label.append(" ");
label.append(StringUtils.uncapitalize(domainProperty.getName()));
label.append(");");


StringBuilder completion = new StringBuilder();
completion.append("List<");
completion.append(repoDef.getDomainType().getSimpleName());
completion.append("> findBy");
completion.append(StringUtils.capitalize(domainProperty.getName()));
completion.append("(");
completion.append(domainProperty.getType().getSimpleName());
completion.append(" ");
completion.append(StringUtils.uncapitalize(domainProperty.getName()));
completion.append(");");

return createProposal(offset, CompletionItemKind.Method, prefix, label.toString(), completion.toString());
}

static ICompletionProposal createProposal(int offset, CompletionItemKind completionItemKind, String prefix, String label, String completion) {
DocumentEdits edits = new DocumentEdits(null, false);
String filter = label;
if (prefix != null && label.startsWith(prefix)) {
edits.replace(offset - prefix.length(), offset, completion);
}
else if (prefix != null && completion.startsWith(prefix)) {
edits.replace(offset - prefix.length(), offset, completion);
filter = completion;
}
else {
edits.insert(offset, completion);
}

DocumentEdits additionalEdits = new DocumentEdits(null, false);
return new FindByCompletionProposal(label, completionItemKind, edits, null, null, Optional.of(additionalEdits), filter);
}

private DataRepositoryDefinition getDataRepositoryDefinition(TypeDeclaration type) {
if (type != null) {
ITypeBinding resolvedType = type.resolveBinding();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ public FindByCompletionProposal(String label, CompletionItemKind kind, DocumentE
this.filter = filter;
}

public static ICompletionProposal createProposal(int offset, CompletionItemKind completionItemKind, String prefix, String label, String completion) {
DocumentEdits edits = new DocumentEdits(null, false);
String filter = label;
if (prefix != null && label.startsWith(prefix)) {
edits.replace(offset - prefix.length(), offset, completion);
}
else if (prefix != null && completion.startsWith(prefix)) {
edits.replace(offset - prefix.length(), offset, completion);
filter = completion;
}
else {
edits.insert(offset, completion);
}

DocumentEdits additionalEdits = new DocumentEdits(null, false);
return new FindByCompletionProposal(label, completionItemKind, edits, null, null, Optional.of(additionalEdits), filter);
}

@Override
public String getLabel() {
return label;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*******************************************************************************
* Copyright (c) 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.data.providers;

import java.util.Collection;

import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.util.text.IDocument;

/**
* Responsible for creating proposals related to Spring Data repositories.
* @author danthe1st
*/
public interface DataRepositoryCompletionProvider {

void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.data.providers;

import java.util.Collection;

import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
import org.springframework.ide.vscode.boot.java.data.FindByCompletionProposal;
import org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive.DataRepositoryPrefixSensitiveCompletionProvider;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.IDocument;

/**
* This class creates text roposals for query method subjects, e.g. {@code countBy}.
* @author danthe1st
*/
public class DataRepositoryQueryStartCompletionProvider implements DataRepositoryCompletionProvider{

@Override
public void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo) {
String localPrefix = DataRepositoryPrefixSensitiveCompletionProvider.findLastJavaIdentifierPart(prefix);
for(QueryMethodSubject queryMethodSubject : QueryMethodSubject.QUERY_METHOD_SUBJECTS){
String toInsert = queryMethodSubject.key() + "By";
if(prefix == null || toInsert.startsWith(localPrefix)||isOffsetAfterWhitespace(doc, offset)) {
completions.add(FindByCompletionProposal.createProposal(offset, CompletionItemKind.Text, prefix, toInsert, toInsert));
}
}
}

private boolean isOffsetAfterWhitespace(IDocument doc, int offset) {
try {
return offset > 0 && Character.isWhitespace(doc.getChar(offset-1));
}catch (BadLocationException e) {
return false;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*******************************************************************************
* Copyright (c) 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.data.providers;

import java.util.Collection;

import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
import org.springframework.ide.vscode.boot.java.data.DomainProperty;
import org.springframework.ide.vscode.boot.java.data.DomainType;
import org.springframework.ide.vscode.boot.java.data.FindByCompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
* Provides content assist proposals for querying by a single attribute in Spring Data repositories.
* @author Martin Lippert
*/
@Component
public class DataRepositoryStandardCompletionProvider implements DataRepositoryCompletionProvider {

public void addProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repo) {
DomainType domainType = repo.getDomainType();
DomainProperty[] properties = domainType.getProperties();
for (DomainProperty property : properties) {
completions.add(generateCompletionProposal(offset, prefix, repo, property));
}
}

private ICompletionProposal generateCompletionProposal(int offset, String prefix, DataRepositoryDefinition repoDef, DomainProperty domainProperty) {
StringBuilder label = new StringBuilder();
label.append("findBy");
label.append(StringUtils.capitalize(domainProperty.getName()));
label.append("(");
label.append(domainProperty.getType().getSimpleName());
label.append(" ");
label.append(StringUtils.uncapitalize(domainProperty.getName()));
label.append(");");

StringBuilder completion = new StringBuilder();
completion.append("List<");
completion.append(repoDef.getDomainType().getSimpleName());
completion.append("> findBy");
completion.append(StringUtils.capitalize(domainProperty.getName()));
completion.append("(");
completion.append(domainProperty.getType().getSimpleName());
completion.append(" ");
completion.append(StringUtils.uncapitalize(domainProperty.getName()));
completion.append(");");

return FindByCompletionProposal.createProposal(offset, CompletionItemKind.Method, prefix, label.toString(), completion.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.data;
package org.springframework.ide.vscode.boot.java.data.providers;

import java.util.List;

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

static final List<QueryMethodSubject> QUERY_METHOD_SUBJECTS = List.of(
public static final List<QueryMethodSubject> QUERY_METHOD_SUBJECTS = List.of(
QueryMethodSubject.createCollectionSubject("find", "List"),
QueryMethodSubject.createCollectionSubject("read", "List"),
QueryMethodSubject.createCollectionSubject("get", "List"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.data;
package org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive;

/**
* Types of predicate keywords Spring JPA repository method names
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.data;
package org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive;

import java.util.List;
import java.util.Set;

import org.springframework.ide.vscode.boot.java.data.providers.QueryMethodSubject;

/**
* Represents the result of parsing a Spring JPA repository query method
* Represents the result of parsing a Spring Data repository query method
* @author danthe1st
*/
record DataRepositoryMethodNameParseResult(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.data;
package org.springframework.ide.vscode.boot.java.data.providers.prefixsensitive;

import java.util.ArrayList;
import java.util.EnumSet;
Expand All @@ -18,6 +18,10 @@
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.ide.vscode.boot.java.data.DataRepositoryDefinition;
import org.springframework.ide.vscode.boot.java.data.DomainProperty;
import org.springframework.ide.vscode.boot.java.data.providers.QueryMethodSubject;

/**
* Class responsible for parsing Spring JPA Repository query methods.
* @author danthe1st
Expand Down
Loading