Spring Data JPA Content Assist
This commit is contained in:
@@ -57,10 +57,13 @@ public class DataRepositoryCompletionProcessor implements CompletionProvider {
|
||||
for (DomainProperty property : properties) {
|
||||
completions.add(generateCompletionProposal(offset, prefix, repo, property));
|
||||
}
|
||||
DataRepositoryPrefixSensitiveCompletionProvider.addPrefixSensitiveProposals(completions, doc, offset, prefix, repo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected ICompletionProposal generateCompletionProposal(int offset, String prefix, DataRepositoryDefinition repoDef, DomainProperty domainProperty) {
|
||||
StringBuilder label = new StringBuilder();
|
||||
label.append("findBy");
|
||||
@@ -71,7 +74,6 @@ public class DataRepositoryCompletionProcessor implements CompletionProvider {
|
||||
label.append(StringUtils.uncapitalize(domainProperty.getName()));
|
||||
label.append(");");
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(null, false);
|
||||
|
||||
StringBuilder completion = new StringBuilder();
|
||||
completion.append("List<");
|
||||
@@ -84,20 +86,25 @@ public class DataRepositoryCompletionProcessor implements CompletionProvider {
|
||||
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) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Types of predicate keywords Spring JPA repository method names
|
||||
* @author danthe1st
|
||||
*/
|
||||
enum DataRepositoryMethodKeywordType {
|
||||
/**
|
||||
* A keyword that terminates an expression.
|
||||
*
|
||||
* e.g. {@code isTrue} in {@code findBySomeBooleanIsTrue}
|
||||
*/
|
||||
TERMINATE_EXPRESSION,
|
||||
/**
|
||||
* An operator combining two conditions.
|
||||
*
|
||||
* e.g. {@code And} in {@code findBySomeAttributeAndAnotherAttribute}
|
||||
*/
|
||||
COMBINE_CONDITIONS,
|
||||
/**
|
||||
* A keyword requiring an expression on both sides or an expression on one side and a parameter.
|
||||
*
|
||||
* e.g. {@code Equals} in {@code findBySomeAttributeEquals} or {@code findBySomeAttributeEqualsAnotherAttribute}
|
||||
*/
|
||||
COMPARE,
|
||||
/**
|
||||
* Keywords that can be ignored for content assist.
|
||||
*
|
||||
* e.g. {@code Not} in {@code findByNotSomeBooleanAttribute}
|
||||
*/
|
||||
IGNORE;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents the result of parsing a Spring JPA repository query method
|
||||
* @author danthe1st
|
||||
*/
|
||||
record DataRepositoryMethodNameParseResult(
|
||||
/**
|
||||
* Information about the subject of the method
|
||||
*/
|
||||
QueryMethodSubject subjectType,
|
||||
/**
|
||||
* parameters required for calling the method
|
||||
*/
|
||||
List<String> parameters,
|
||||
/**
|
||||
* {@code true} if the whole method shall be replaced including parameters, else false
|
||||
*/
|
||||
boolean performFullCompletion,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* types of keywords that can be completed with
|
||||
*/
|
||||
Set<DataRepositoryMethodKeywordType> allowedKeywordTypes) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Class responsible for parsing Spring JPA Repository query methods.
|
||||
* @author danthe1st
|
||||
*/
|
||||
class DataRepositoryMethodParser {
|
||||
|
||||
private static final Map<String, List<QueryPredicateKeywordInfo>> PREDICATE_KEYWORDS_GROUPED_BY_FIRST_WORD = QueryPredicateKeywordInfo.PREDICATE_KEYWORDS
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(info->{
|
||||
return findFirstWord(info.keyword());
|
||||
}));
|
||||
|
||||
private final String prefix;
|
||||
private final Map<String, List<DomainProperty>> propertiesGroupedByFirstWord;
|
||||
private String expectedNextType = null;//the expected type as string if a type is expected, if the type cannot be found, the user should supply it
|
||||
private boolean performFullCompletion = true;//if some invalid text is detected, do not complete the whole method
|
||||
private String previousExpression = null;
|
||||
|
||||
public DataRepositoryMethodParser(String localPrefix, DataRepositoryDefinition repoDef) {
|
||||
prefix = localPrefix;
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
QueryMethodSubject subjectType = parseSubject(subjectPredicateSplitIndex);
|
||||
if (subjectType == null) {
|
||||
return null;
|
||||
}
|
||||
String predicate = prefix.substring(subjectPredicateSplitIndex + 2);
|
||||
List<String> parameters=new ArrayList<>();
|
||||
|
||||
parsePredicate(predicate, parameters);
|
||||
|
||||
EnumSet<DataRepositoryMethodKeywordType> allowedKeywordTypes = findAllowedKeywordTypesAtEnd();
|
||||
return new DataRepositoryMethodNameParseResult(subjectType, parameters, performFullCompletion, previousExpression, allowedKeywordTypes);
|
||||
}
|
||||
|
||||
private void parsePredicate(String predicate, List<String> parameters) {
|
||||
int lastWordEnd = 0;
|
||||
|
||||
for (int i = 1; i <= predicate.length(); i++) {
|
||||
if(i == predicate.length() || Character.isUpperCase(predicate.charAt(i))) {//word ends on uppercase letter or end of string
|
||||
String word = predicate.substring(lastWordEnd, i);
|
||||
QueryPredicateKeywordInfo keyword = findByLargestFirstWord(PREDICATE_KEYWORDS_GROUPED_BY_FIRST_WORD, QueryPredicateKeywordInfo::keyword, predicate, lastWordEnd, word);
|
||||
if (keyword == null){
|
||||
DomainProperty preferredWord = findByLargestFirstWord(propertiesGroupedByFirstWord, DomainProperty::getName, predicate, lastWordEnd, word);
|
||||
if (preferredWord != null) {
|
||||
i += preferredWord.getName().length() - word.length();
|
||||
word=preferredWord.getName();
|
||||
}
|
||||
parseNonKeyword(word);
|
||||
} else {
|
||||
i += keyword.keyword().length() - word.length();
|
||||
parseKeyword(parameters, keyword);
|
||||
}
|
||||
lastWordEnd = i;
|
||||
}
|
||||
}
|
||||
if (expectedNextType != null) {
|
||||
parameters.add(expectedNextType);
|
||||
}
|
||||
}
|
||||
|
||||
private QueryMethodSubject parseSubject(int subjectPredicateSplitIndex) {
|
||||
String subject = prefix.substring(0, subjectPredicateSplitIndex);
|
||||
QueryMethodSubject subjectType = null;
|
||||
for(QueryMethodSubject queryMethodSubject : QueryMethodSubject.QUERY_METHOD_SUBJECTS){
|
||||
if(subject.startsWith(queryMethodSubject.key())) {
|
||||
subjectType = queryMethodSubject;
|
||||
}
|
||||
}
|
||||
return subjectType;
|
||||
}
|
||||
|
||||
private EnumSet<DataRepositoryMethodKeywordType> findAllowedKeywordTypesAtEnd() {
|
||||
EnumSet<DataRepositoryMethodKeywordType> allowedKeywordTypes = EnumSet.allOf(DataRepositoryMethodKeywordType.class);
|
||||
if (expectedNextType == null) {
|
||||
allowedKeywordTypes.remove(DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION);
|
||||
allowedKeywordTypes.remove(DataRepositoryMethodKeywordType.COMPARE);
|
||||
}
|
||||
return allowedKeywordTypes;
|
||||
}
|
||||
|
||||
private void parseNonKeyword(String word) {
|
||||
if (previousExpression == null) {
|
||||
previousExpression = word;
|
||||
//non-keywords just invert the status
|
||||
//if an expression is expected, the word is the expression
|
||||
//if not, some expression is required after the word
|
||||
if (expectedNextType == null) {
|
||||
expectedNextType = word;
|
||||
} else {
|
||||
expectedNextType = null;
|
||||
}
|
||||
} else {
|
||||
//combine multiple words that are not keywords
|
||||
previousExpression += word;
|
||||
if (expectedNextType != null) {
|
||||
expectedNextType = previousExpression;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseKeyword(List<String> parameters, QueryPredicateKeywordInfo keyword) {
|
||||
switch(keyword.type()) {
|
||||
case TERMINATE_EXPRESSION: {//e.g. IsTrue
|
||||
if (expectedNextType == null) {
|
||||
//if no next type/expression is expected (which should not happen), do not complete the full method (parameters)
|
||||
performFullCompletion = false;
|
||||
}
|
||||
expectedNextType = null;
|
||||
|
||||
break;
|
||||
}
|
||||
case COMBINE_CONDITIONS: {//e.g. And
|
||||
//if an expression is expected, it is added to the parameters
|
||||
if (expectedNextType != null) {
|
||||
parameters.add(expectedNextType);
|
||||
}
|
||||
expectedNextType = null;
|
||||
break;
|
||||
}
|
||||
case COMPARE: {//e.g. GreaterThan
|
||||
if (expectedNextType == null) {
|
||||
//nothing to compare, e.g. And directly followed by GreaterThan
|
||||
performFullCompletion = false;
|
||||
}
|
||||
expectedNextType = previousExpression;
|
||||
break;
|
||||
}
|
||||
case IGNORE:{
|
||||
//ignore
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected value: " + keyword.type());
|
||||
}
|
||||
previousExpression = null;
|
||||
}
|
||||
|
||||
private <T> T findByLargestFirstWord(Map<String, List<T>> toSearch, Function<T, String> expressionExtractor, String predicate, int lastWordEnd, String word) {
|
||||
T ret = null;
|
||||
if (toSearch.containsKey(word)) {
|
||||
for(T possibleKeyword : toSearch.get(word)){
|
||||
int endPosition = lastWordEnd + expressionExtractor.apply(possibleKeyword).length();
|
||||
if (predicate.length() >= endPosition
|
||||
&& expressionExtractor.apply(possibleKeyword).equals(predicate.substring(lastWordEnd, endPosition))
|
||||
&& (ret == null || expressionExtractor.apply(possibleKeyword).length() > expressionExtractor.apply(possibleKeyword).length())) {//find largest valid keyword
|
||||
ret = possibleKeyword;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static String findFirstWord(String expression) {
|
||||
int firstWordEnd;
|
||||
for (firstWordEnd = 1;
|
||||
firstWordEnd < expression.length()
|
||||
&& Character.isLowerCase(expression.charAt(firstWordEnd));
|
||||
firstWordEnd++) {
|
||||
//search is done in loop condition
|
||||
}
|
||||
return expression.substring(0, firstWordEnd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
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.util.StringUtils;
|
||||
|
||||
/**
|
||||
* This utility class provides content assist proposals for Spring JPA query methods.
|
||||
* @author danthe1st
|
||||
*/
|
||||
class DataRepositoryPrefixSensitiveCompletionProvider {
|
||||
|
||||
private DataRepositoryPrefixSensitiveCompletionProvider() {
|
||||
//prevent instantiation
|
||||
}
|
||||
|
||||
static void addPrefixSensitiveProposals(Collection<ICompletionProposal> completions, IDocument doc, int offset, String prefix, DataRepositoryDefinition repoDef) {
|
||||
String localPrefix = findLastJavaIdentifierPart(prefix);
|
||||
if (localPrefix == null) {
|
||||
return;
|
||||
}
|
||||
DataRepositoryMethodNameParseResult parseResult = new DataRepositoryMethodParser(localPrefix, repoDef).parseLocalPrefixForCompletion();
|
||||
if(parseResult != null && parseResult.performFullCompletion()){
|
||||
Map<String, DomainProperty> propertiesByName = getPropertiesByName(repoDef.getDomainType().getProperties());
|
||||
addMethodCompletionProposal(completions, offset, repoDef, localPrefix, prefix, parseResult, propertiesByName);
|
||||
|
||||
if (parseResult.lastWord() == null || !propertiesByName.containsKey(parseResult.lastWord())) {
|
||||
addPropertyProposals(completions, offset, repoDef, parseResult);
|
||||
}
|
||||
}
|
||||
addQueryStartProposals(completions, localPrefix, doc, offset);
|
||||
}
|
||||
|
||||
private static void addQueryStartProposals(Collection<ICompletionProposal> completions, String prefix, IDocument doc, int offset) {
|
||||
for(QueryMethodSubject queryMethodSubject : QueryMethodSubject.QUERY_METHOD_SUBJECTS){
|
||||
String toInsert = queryMethodSubject.key() + "By";
|
||||
if(toInsert.startsWith(prefix)||isOffsetAfterWhitespace(doc, offset)) {
|
||||
completions.add(DataRepositoryCompletionProcessor.createProposal(offset, CompletionItemKind.Text, prefix, toInsert, toInsert));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isOffsetAfterWhitespace(IDocument doc, int offset) {
|
||||
try {
|
||||
return offset > 0 && Character.isWhitespace(doc.getChar(offset-1));
|
||||
}catch (BadLocationException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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, String fullPrefix, 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(">");
|
||||
}
|
||||
String returnType = newText.toString();
|
||||
newText.append(" ");
|
||||
newText.append(signature);
|
||||
newText.append(";");
|
||||
int replaceStart = calculateReplaceOffset(offset, localPrefix, fullPrefix, returnType);
|
||||
edits.replace(replaceStart, 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 int calculateReplaceOffset(int offset, String localPrefix, String fullPrefix, String returnType) {
|
||||
int replaceStart = offset - localPrefix.length();
|
||||
String beforeLocalPrefix = fullPrefix.substring(0, fullPrefix.length()-localPrefix.length());
|
||||
String trimmed = beforeLocalPrefix.trim();
|
||||
if(trimmed.endsWith(returnType)) {
|
||||
replaceStart -= (beforeLocalPrefix.length() - trimmed.length()) + returnType.length();
|
||||
}
|
||||
return replaceStart;
|
||||
}
|
||||
|
||||
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);
|
||||
DomainType type = findExpressionType(properties, param);
|
||||
if (type == null) {
|
||||
signatureBuilder.append("Object");
|
||||
}else {
|
||||
signatureBuilder.append(type.getSimpleName());
|
||||
}
|
||||
signatureBuilder.append(" ");
|
||||
signatureBuilder.append(StringUtils.uncapitalize(param));
|
||||
if (i + 1 < parameters.size()) {
|
||||
signatureBuilder.append(", ");
|
||||
}
|
||||
}
|
||||
signatureBuilder.append(")");
|
||||
return signatureBuilder.toString();
|
||||
}
|
||||
|
||||
private static DomainType findExpressionType(Map<String, DomainProperty> properties, String param) {
|
||||
String[] splitByUnderscore = param.split("_");
|
||||
if(properties.containsKey(splitByUnderscore[0])) {
|
||||
DomainType type = properties.get(splitByUnderscore[0]).getType();
|
||||
for (int j = 1; j < splitByUnderscore.length && type != null; j++) {
|
||||
type = findMatchingParameter(splitByUnderscore[j], type);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DomainType findMatchingParameter(String name, DomainType type) {
|
||||
for(DomainProperty prop : type.getProperties()){
|
||||
if (prop.getName().equals(name)) {
|
||||
return prop.getType();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String findLastJavaIdentifierPart(String prefix) {
|
||||
if (prefix == null) {
|
||||
return null;
|
||||
}
|
||||
int lastNonIdentifierPartIndex;
|
||||
for (lastNonIdentifierPartIndex = prefix.length() - 1; lastNonIdentifierPartIndex >= 0 && Character.isJavaIdentifierPart(prefix.charAt(lastNonIdentifierPartIndex)); lastNonIdentifierPartIndex--) {
|
||||
// search done using loop condition
|
||||
}
|
||||
return prefix.substring(lastNonIdentifierPartIndex + 1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -36,7 +36,11 @@ public class DomainType {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -48,9 +52,17 @@ public class DomainType {
|
||||
|
||||
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()]);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents information about the subject of a JPA query method.
|
||||
*
|
||||
* See https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#appendix.query.method.subject
|
||||
* @author danthe1st
|
||||
*/
|
||||
record QueryMethodSubject(
|
||||
String key, String returnType, boolean isTyped) {
|
||||
|
||||
static final List<QueryMethodSubject> QUERY_METHOD_SUBJECTS = List.of(
|
||||
QueryMethodSubject.createCollectionSubject("find", "List"),
|
||||
QueryMethodSubject.createCollectionSubject("read", "List"),
|
||||
QueryMethodSubject.createCollectionSubject("get", "List"),
|
||||
QueryMethodSubject.createCollectionSubject("query", "List"),
|
||||
QueryMethodSubject.createCollectionSubject("search", "List"),
|
||||
QueryMethodSubject.createCollectionSubject("stream", "Streamable"),
|
||||
QueryMethodSubject.createPrimitiveSubject("exists", "boolean"),
|
||||
QueryMethodSubject.createPrimitiveSubject("count", "long"),
|
||||
QueryMethodSubject.createPrimitiveSubject("delete", "void"),
|
||||
QueryMethodSubject.createPrimitiveSubject("remove", "void")
|
||||
);
|
||||
|
||||
private static QueryMethodSubject createPrimitiveSubject(String key, String primitive) {
|
||||
return new QueryMethodSubject(key, primitive, false);
|
||||
}
|
||||
private static QueryMethodSubject createCollectionSubject(String key, String collectionType) {
|
||||
return new QueryMethodSubject(key, collectionType, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
* 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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents information about the predicate in Spring JPA repository query methods.
|
||||
*
|
||||
* See https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#appendix.query.method.predicate
|
||||
*
|
||||
* @author danthe1st
|
||||
*/
|
||||
record QueryPredicateKeywordInfo(String keyword, DataRepositoryMethodKeywordType type) {
|
||||
static final List<QueryPredicateKeywordInfo> PREDICATE_KEYWORDS = List.of(
|
||||
new QueryPredicateKeywordInfo("And", DataRepositoryMethodKeywordType.COMBINE_CONDITIONS),
|
||||
new QueryPredicateKeywordInfo("Or", DataRepositoryMethodKeywordType.COMBINE_CONDITIONS),
|
||||
new QueryPredicateKeywordInfo("After", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsAfter", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Before", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsBefore", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Containing", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsContaining", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Contains", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Between", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsBetween", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("EndingWith", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsEndingWith", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("EndsWith", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Exists", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("False", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("IsFalse", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("GreaterThan", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsGreaterThan", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("GreaterThanEqual", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsGreaterThanEqual", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("In", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsIn", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Is", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Equals", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Empty", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("IsEmpty", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("NotEmpty", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("IsNotEmpty", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("NotNull", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("IsNotNull", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("Null", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("IsNull", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("LessThan", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsLessThan", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("LessThanEqual", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsLessThanEqual", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Like", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsLike", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Near", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsNear", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Not", DataRepositoryMethodKeywordType.IGNORE),
|
||||
new QueryPredicateKeywordInfo("IsNot", DataRepositoryMethodKeywordType.IGNORE),
|
||||
new QueryPredicateKeywordInfo("NotIn", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsNotIn", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("NotLike", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsNotLike", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Regex", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("MatchesRegex", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("Matches", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("StartingWith", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsStartingWith", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("StartsWith", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("True", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("IsTrue", DataRepositoryMethodKeywordType.TERMINATE_EXPRESSION),
|
||||
new QueryPredicateKeywordInfo("Within", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IsWithin", DataRepositoryMethodKeywordType.COMPARE),
|
||||
new QueryPredicateKeywordInfo("IgnoreCase", DataRepositoryMethodKeywordType.IGNORE),
|
||||
new QueryPredicateKeywordInfo("IgnoringCase", DataRepositoryMethodKeywordType.IGNORE),
|
||||
new QueryPredicateKeywordInfo("AllIgnoreCase", DataRepositoryMethodKeywordType.IGNORE),
|
||||
new QueryPredicateKeywordInfo("AllIgnoringCase", DataRepositoryMethodKeywordType.IGNORE),
|
||||
new QueryPredicateKeywordInfo("OrderBy", DataRepositoryMethodKeywordType.COMBINE_CONDITIONS)
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -27,13 +28,13 @@ import org.springframework.ide.vscode.boot.bootiful.HoverTestConf;
|
||||
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
|
||||
@@ -53,11 +54,100 @@ public class DataRepositoryCompletionProcessorTest {
|
||||
@Test
|
||||
void testStandardFindByCompletions() throws Exception {
|
||||
prepareCase("{", "{<*>");
|
||||
assertContainsAnnotationCompletions(
|
||||
"List<Customer> findByFirstName(String firstName);",
|
||||
"List<Customer> findByLastName(String lastName);");
|
||||
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> 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 testPrefixSensitiveCompletionsCompleteMethodReturnTypePresent() throws Exception {
|
||||
checkCompletions("List<Customer> findByFirstNameAndLastName", "List<Customer> findByFirstNameAndLastName(String firstName, String lastName);");
|
||||
checkCompletions("boolean existsByFirstNameAndLastName", "boolean existsByFirstNameAndLastName(String firstName, String lastName);");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAttributeComparison() throws Exception {
|
||||
checkCompletions("findByFirstNameIsGreaterThanLastName", "List<Customer> findByFirstNameIsGreaterThanLastName();");
|
||||
checkCompletions("findByFirstNameIsLastName", "List<Customer> findByFirstNameIsLastName();");
|
||||
}
|
||||
|
||||
@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);");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPropertyProposals() throws Exception {
|
||||
checkCompletions("findByFirst", "findByFirstName");
|
||||
checkCompletions("findByFirstNameAndL", "findByFirstNameAndLastName");
|
||||
checkCompletions("findBy",
|
||||
"findByFirstName",
|
||||
"findByLastName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByComplexExpression() throws Exception {
|
||||
checkCompletions("findByResponsibleEmployee", "List<Customer> findByResponsibleEmployee(Employee responsibleEmployee);");
|
||||
checkCompletions("findByResponsibleEmployee_SocialSecurityNumber", "List<Customer> findByResponsibleEmployee_SocialSecurityNumber(Long responsibleEmployee_SocialSecurityNumber);");
|
||||
}
|
||||
|
||||
private void checkCompletions(String alredyPresent, String... expectedCompletions) throws Exception {
|
||||
prepareCase("{\n}", "{\n\t" + alredyPresent + "<*>");
|
||||
assertContainsAnnotationCompletions(Arrays.stream(expectedCompletions).map(expected -> "\t" + 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);
|
||||
@@ -74,13 +164,10 @@ public class DataRepositoryCompletionProcessorTest {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.test;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@@ -19,18 +18,19 @@ public class Application {
|
||||
|
||||
@Bean
|
||||
public CommandLineRunner demo(CustomerRepository repository) {
|
||||
return (args) -> {
|
||||
return args -> {
|
||||
Employee employee = new Employee(1234, "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("");
|
||||
@@ -45,7 +45,7 @@ public class Application {
|
||||
// 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("");
|
||||
|
||||
@@ -5,29 +5,34 @@ import javax.persistence.Entity;
|
||||
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[]
|
||||
|
||||
@@ -42,5 +47,12 @@ public class Customer {
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isThisCustomerIsSpecial() {
|
||||
return thisCustomerIsSpecial;
|
||||
}
|
||||
|
||||
public Employee getResponsibleEmployee() {
|
||||
return responsibleEmployee;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
private Long socialSecurityNumber;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
|
||||
protected Employee() {
|
||||
}
|
||||
|
||||
public Employee(long socialSecurityNumber, String firstName, String lastName) {
|
||||
this.socialSecurityNumber = socialSecurityNumber;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"Employee[socialSecurityNumber=%d, firstName='%s', lastName='%s']",
|
||||
socialSecurityNumber, firstName, lastName
|
||||
);
|
||||
}
|
||||
|
||||
// end::sample[]
|
||||
|
||||
public Long getSocialSecurityNumber() {
|
||||
return socialSecurityNumber;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface TestCustomerRepositoryForCompletions extends CrudRepository<Customer, Long> {
|
||||
|
||||
Reference in New Issue
Block a user