Skip to content

Commit

Permalink
Spring Data JPA Content Assist
Browse files Browse the repository at this point in the history
  • Loading branch information
danthe1st committed Feb 21, 2023
1 parent 9006d6e commit 366f715
Show file tree
Hide file tree
Showing 8 changed files with 552 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public void provideCompletions(ASTNode node, int offset, IDocument doc, Collecti
for (DomainProperty property : properties) {
completions.add(generateCompletionProposal(offset, prefix, repo, property));
}
DataRepositoryPrefixSensitiveCompletionProvider.addPrefixSensitiveProposals(completions, offset, prefix, repo);
}
}
}
Expand All @@ -71,7 +72,6 @@ protected ICompletionProposal generateCompletionProposal(int offset, String pref
label.append(StringUtils.uncapitalize(domainProperty.getName()));
label.append(");");

DocumentEdits edits = new DocumentEdits(null, false);

StringBuilder completion = new StringBuilder();
completion.append("List<");
Expand All @@ -84,20 +84,25 @@ protected ICompletionProposal generateCompletionProposal(int offset, String pref
completion.append(StringUtils.uncapitalize(domainProperty.getName()));
completion.append(");");

String filter = label.toString();
if (prefix != null && label.toString().startsWith(prefix)) {
edits.replace(offset - prefix.length(), offset, completion.toString());
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.toString().startsWith(prefix)) {
edits.replace(offset - prefix.length(), offset, completion.toString());
filter = completion.toString();
else if (prefix != null && completion.startsWith(prefix)) {
edits.replace(offset - prefix.length(), offset, completion);
filter = completion;
}
else {
edits.insert(offset, completion.toString());
edits.insert(offset, completion);
}

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

private DataRepositoryDefinition getDataRepositoryDefinition(TypeDeclaration type) {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ public DomainType(String packageName, String fullName, String simpleName) {
}

public DomainType(ITypeBinding typeBinding) {
this.packageName = typeBinding.getPackage().getName();
if (typeBinding.getPackage() == null) {
this.packageName = "";
} else {
this.packageName = typeBinding.getPackage().getName();
}
this.fullName = typeBinding.getQualifiedName();
this.simpleName = typeBinding.getName();

Expand All @@ -48,9 +52,17 @@ public DomainType(ITypeBinding typeBinding) {

for (IMethodBinding method : methods) {
String methodName = method.getName();
if (methodName != null && methodName.startsWith("get")) {
String propertyName = methodName.substring(3);
properties.add(new DomainProperty(propertyName, new DomainType(method.getReturnType())));
if (methodName != null) {
String propertyName = null;
if (methodName.startsWith("get")) {
propertyName = methodName.substring(3);
}
else if (methodName.startsWith("is")) {
propertyName = methodName.substring(2);
}
if (propertyName != null) {
properties.add(new DomainProperty(propertyName, new DomainType(method.getReturnType())));
}
}
}
return (DomainProperty[]) properties.toArray(new DomainProperty[properties.size()]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* Copyright (c) 2018, 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
Expand All @@ -11,6 +11,7 @@
package org.springframework.ide.vscode.boot.java.data.test;

import java.io.InputStream;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.io.IOUtils;
Expand All @@ -27,13 +28,13 @@
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.TestAsserts;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit.jupiter.SpringExtension;

/**
* @author Martin Lippert
* @author danthe1st
*/
@ExtendWith(SpringExtension.class)
@BootLanguageServerTest
Expand All @@ -53,11 +54,78 @@ public void setup() throws Exception {
@Test
void testStandardFindByCompletions() throws Exception {
prepareCase("{", "{<*>");
assertContainsAnnotationCompletions(
assertStandardCompletions();
}

@Test
void testPrefixSensitiveCompletionsNoPrefix() throws Exception {
prepareCase("{\n}", "{\n<*>");
assertStandardCompletions();
}

private void assertStandardCompletions() throws Exception {
assertContainsAnnotationCompletions(
"countBy",
"deleteBy",
"existsBy",
"findBy",
"List<Customer> findByFirstName(String firstName);",
"List<Customer> findByLastName(String lastName);");
"List<Customer> findById(Long id);",
"List<Customer> findByLastName(String lastName);",
"List<Customer> findByResponsibleEmployee(Employee responsibleEmployee);",
"getBy",
"queryBy",
"readBy",
"removeBy",
"searchBy",
"streamBy");
}

@Test
void testPrefixSensitiveCompletionsCompleteMethod() throws Exception {
checkCompletions("findByFirstNameAndLastName", "List<Customer> findByFirstNameAndLastName(String firstName, String lastName);");
}

@Test
void testAttributeComparison() throws Exception {
checkCompletions("findByFirstNameIsGreaterThanLastName", "List<Customer> findByFirstNameIsGreaterThanLastName();");
}

@Test
void testTerminatingKeyword() throws Exception {
checkCompletions("findByFirstNameNull", "List<Customer> findByFirstNameNull();");
checkCompletions("findByFirstNameNotNull", "List<Customer> findByFirstNameNotNull();");
}

@Test
void testNewConditionAfterTerminatedExpression() throws Exception {
checkCompletions("findByFirstNameNullAndLastName", "List<Customer> findByFirstNameNullAndLastName(String lastName);");
checkCompletions("findByNotFirstNameNullAndNotLastName", "List<Customer> findByNotFirstNameNullAndNotLastName(String lastName);");
}

@Test
void testDifferentSubjectTypes() throws Exception {
checkCompletions("existsByFirstName", "boolean existsByFirstName(String firstName);");
checkCompletions("countByFirstName", "long countByFirstName(String firstName);");
checkCompletions("streamByFirstName", "Streamable<Customer> streamByFirstName(String firstName);");
checkCompletions("removeByFirstName", "void removeByFirstName(String firstName);");
}

@Test
void testUnknownAttribute() throws Exception {
checkCompletions("findByUnknownObject", "List<Customer> findByUnknownObject(Object unknownObject);");
}

@Test
void testKeywordInPredicate() throws Exception {
checkCompletions("findByThisCustomerIsSpecial", "List<Customer> findByThisCustomerIsSpecial(boolean thisCustomerIsSpecial);");
}

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

private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception {
InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-spring-data-symbols/src/main/java/org/test/TestCustomerRepositoryForCompletions.java");
String content = IOUtils.toString(resource);
Expand All @@ -74,13 +142,10 @@ private void assertContainsAnnotationCompletions(String... expectedResultsFromCo
Editor clonedEditor = editor.clone();
clonedEditor.apply(foundCompletion);

if (clonedEditor.getText().contains(expectedResultsFromCompletion[i])) {
if (i < expectedResultsFromCompletion.length && clonedEditor.getText().contains(expectedResultsFromCompletion[i])) {
i++;
}
}

assertEquals(expectedResultsFromCompletion.length, i);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,54 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {

private static final Logger log = LoggerFactory.getLogger(Application.class);

public static void main(String[] args) {
SpringApplication.run(Application.class);
}

@Bean
public CommandLineRunner demo(CustomerRepository repository) {
return (args) -> {
return args -> {
Employee employee = new Employee("Margot", "Al-Harazi");
// save a couple of customers
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));

repository.save(new Customer("Jack", "Bauer", employee));
repository.save(new Customer("Chloe", "O'Brian", employee));
repository.save(new Customer("Kim", "Bauer", employee));
repository.save(new Customer("David", "Palmer", employee));
repository.save(new Customer("Michelle", "Dessler", employee));
// fetch all customers
log.info("Customers found with findAll():");
log.info("-------------------------------");
for (Customer customer : repository.findAll()) {
for(Customer customer : repository.findAll()){
log.info(customer.toString());
}
log.info("");

// fetch an individual customer by ID
Customer customer = repository.findOne(1L);
log.info("Customer found with findOne(1L):");
log.info("--------------------------------");
log.info(customer.toString());
log.info("");

// fetch customers by last name
log.info("Customer found with findByLastName('Bauer'):");
log.info("--------------------------------------------");
for (Customer bauer : repository.findByLastName("Bauer")) {
for(Customer bauer : repository.findByLastName("Bauer")){
log.info(bauer.toString());
}
log.info("");
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,34 @@
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

@Entity
public class Customer {

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private boolean thisCustomerIsSpecial;//contains keyword in name

protected Customer() {}
@ManyToOne
private Employee responsibleEmployee;

public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
protected Customer() {}

@Override
public String toString() {
return String.format(
"Customer[id=%d, firstName='%s', lastName='%s']",
id, firstName, lastName);
}
public Customer(String firstName, String lastName, Employee responsibleEmployee) {
this.firstName = firstName;
this.lastName = lastName;
this.responsibleEmployee = responsibleEmployee;
}

@Override
public String toString() {
return "Customer [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", responsibleEmployee="
+ responsibleEmployee + "]";
}

// end::sample[]

Expand All @@ -42,5 +47,12 @@ public String getFirstName() {
public String getLastName() {
return lastName;
}
}

public boolean isThisCustomerIsSpecial() {
return thisCustomerIsSpecial;
}

public Employee getResponsibleEmployee() {
return responsibleEmployee;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// tag::sample[]
package org.test;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Employee {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;

protected Employee() {
}

public Employee(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

@Override
public String toString() {
return String.format(
"Employee[id=%d, firstName='%s', lastName='%s']",
id, firstName, lastName
);
}

// end::sample[]

public Long getId() {
return id;
}

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}
}
Loading

0 comments on commit 366f715

Please sign in to comment.