Skip to content

Commit

Permalink
Factor out static methods for Spring Data repository completions
Browse files Browse the repository at this point in the history
  • Loading branch information
danthe1st authored and BoykoAlex committed Feb 28, 2023
1 parent 75fcb63 commit 64fabdf
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 109 deletions.
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,61 @@
/*******************************************************************************
* 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.util.StringUtils;

/**
* Provides content assist proposals for querying by a single attribute in Spring Data repositories.
* @author Martin Lippert
*/
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

0 comments on commit 64fabdf

Please sign in to comment.